authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 01:06:37-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 01:06:37-07:00
log2fa37ec183e0b99ca2ea77efbcb615f11019da75
tree5f9b229ed26c6b6fffb244af091a272d71bd6534
parentdb27502aff1add34eb2af4ff12c606e99bdc3610
signature Signed by PGP key B5690EEEBB952194

[codex] Enforce canonical Node namespace imports (#3238)

Co-authored-by: codex <codex@users.noreply.github.com>

71 files changed, 1205 insertions(+), 952 deletions(-)

.macroscope/check-run-agents/effect-service-conventions.md+1-1
...@@ -27,7 +27,7 @@ Review changed TypeScript and directly affected call sites for the conventions b...@@ -27,7 +27,7 @@ Review changed TypeScript and directly affected call sites for the conventions b
2727
28- Import Effect library modules from their subpaths as namespaces, for example `import * as Effect from "effect/Effect"` and `import * as Layer from "effect/Layer"`. Flag consolidated named imports from `"effect"` in touched Effect service code.28- Import Effect library modules from their subpaths as namespaces, for example `import * as Effect from "effect/Effect"` and `import * as Layer from "effect/Layer"`. Flag consolidated named imports from `"effect"` in touched Effect service code.
29- At a service boundary, import the local service module as a namespace and use its public module shape: `WorkspacePaths.WorkspacePaths`, `WorkspacePaths.make`, and `WorkspacePaths.layer`. Flag aliases such as `import { layer as workspacePathsLayer }` that erase the module namespace.29- At a service boundary, import the local service module as a namespace and use its public module shape: `WorkspacePaths.WorkspacePaths`, `WorkspacePaths.make`, and `WorkspacePaths.layer`. Flag aliases such as `import { layer as workspacePathsLayer }` that erase the module namespace.
30- Namespace imports are not a blanket rule. Keep named imports for whole packages such as `@t3tools/contracts` and `electron`, and for modules used only for a pure helper, error, schema, config value, or standalone type. Do not request `import type * as Contracts`.30- Namespace imports are not a blanket rule. Keep named imports for whole packages such as `@t3tools/contracts`, and for modules used only for a pure helper, error, schema, config value, or standalone type. Do not request `import type * as Contracts`.
31- A package subpath that is itself a service module may use a namespace import when callers access its service/tag, `make`, or `layer` members.31- A package subpath that is itself a service module may use a namespace import when callers access its service/tag, `make`, or `layer` members.
32- When a barrel exposes an entire service module, prefer `export * as TokenStore from "./tokenStore.ts"` so consumers can use `TokenStore.TokenStore` and `TokenStore.layer`. Do not individually rename `make` and `layer` exports to simulate a namespace.32- When a barrel exposes an entire service module, prefer `export * as TokenStore from "./tokenStore.ts"` so consumers can use `TokenStore.TokenStore` and `TokenStore.layer`. Do not individually rename `make` and `layer` exports to simulate a namespace.
3333
apps/desktop/scripts/build-preview-annotation-css.mjs+16-16
...@@ -1,23 +1,23 @@...@@ -1,23 +1,23 @@
1import { readFile, writeFile } from "node:fs/promises";1import * as NodeFSP from "node:fs/promises";
2import { createRequire } from "node:module";2import * as NodeModule from "node:module";
3import { dirname, join } from "node:path";3import * as NodePath from "node:path";
4import { fileURLToPath } from "node:url";4import * as NodeURL from "node:url";
55
6import { compile } from "tailwindcss";6import { compile } from "tailwindcss";
77
8const directory = dirname(fileURLToPath(import.meta.url));8const directory = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
9const appRoot = join(directory, "..");9const appRoot = NodePath.join(directory, "..");
10const sourcePath = join(appRoot, "src", "preview", "Annotation.css");10const sourcePath = NodePath.join(appRoot, "src", "preview", "Annotation.css");
11const preloadPath = join(appRoot, "src", "preview", "PickPreload.ts");11const preloadPath = NodePath.join(appRoot, "src", "preview", "PickPreload.ts");
12const outputPath = join(appRoot, "src", "preview", "AnnotationStyles.generated.ts");12const outputPath = NodePath.join(appRoot, "src", "preview", "AnnotationStyles.generated.ts");
13const require = createRequire(import.meta.url);13const require = NodeModule.createRequire(import.meta.url);
14const tailwindRoot = dirname(require.resolve("tailwindcss/package.json"));14const tailwindRoot = NodePath.dirname(require.resolve("tailwindcss/package.json"));
1515
16const [annotationSource, preloadSource, themeSource, preflightSource] = await Promise.all([16const [annotationSource, preloadSource, themeSource, preflightSource] = await Promise.all([
17 readFile(sourcePath, "utf8"),17 NodeFSP.readFile(sourcePath, "utf8"),
18 readFile(preloadPath, "utf8"),18 NodeFSP.readFile(preloadPath, "utf8"),
19 readFile(join(tailwindRoot, "theme.css"), "utf8"),19 NodeFSP.readFile(NodePath.join(tailwindRoot, "theme.css"), "utf8"),
20 readFile(join(tailwindRoot, "preflight.css"), "utf8"),20 NodeFSP.readFile(NodePath.join(tailwindRoot, "preflight.css"), "utf8"),
21]);21]);
2222
23const candidates = new Set(23const candidates = new Set(
...@@ -37,4 +37,4 @@ const encodedCss = `'${css...@@ -37,4 +37,4 @@ const encodedCss = `'${css
37 .replaceAll("\n", "\\n")}'`;37 .replaceAll("\n", "\\n")}'`;
38const moduleSource = `// Generated by scripts/build-preview-annotation-css.mjs. Do not edit.\nexport const previewAnnotationStyles =\n ${encodedCss};\n`;38const moduleSource = `// Generated by scripts/build-preview-annotation-css.mjs. Do not edit.\nexport const previewAnnotationStyles =\n ${encodedCss};\n`;
3939
40await writeFile(outputPath, moduleSource);40await NodeFSP.writeFile(outputPath, moduleSource);
apps/desktop/scripts/dev-electron.mjs+13-9
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import { spawn, spawnSync } from "node:child_process";1import * as NodeChildProcess from "node:child_process";
2import { watch } from "node:fs";2import * as NodeFS from "node:fs";
3import * as NodeOS from "node:os";3import * as NodeOS from "node:os";
4import { join } from "node:path";4import * as NodePath from "node:path";
55
6import {6import {
7 desktopDir,7 desktopDir,
...@@ -64,7 +64,7 @@ function killChildTreeByPid(pid, signal) {...@@ -64,7 +64,7 @@ function killChildTreeByPid(pid, signal) {
64 return;64 return;
65 }65 }
6666
67 spawnSync("pkill", [`-${signal}`, "-P", String(pid)], { stdio: "ignore" });67 NodeChildProcess.spawnSync("pkill", [`-${signal}`, "-P", String(pid)], { stdio: "ignore" });
68}68}
6969
70function cleanupStaleDevApps() {70function cleanupStaleDevApps() {
...@@ -72,7 +72,9 @@ function cleanupStaleDevApps() {...@@ -72,7 +72,9 @@ function cleanupStaleDevApps() {
72 return;72 return;
73 }73 }
7474
75 spawnSync("pkill", ["-f", "--", `--t3code-dev-root=${desktopDir}`], { stdio: "ignore" });75 NodeChildProcess.spawnSync("pkill", ["-f", "--", `--t3code-dev-root=${desktopDir}`], {
76 stdio: "ignore",
77 });
76}78}
7779
78function startApp() {80function startApp() {
...@@ -87,7 +89,7 @@ function startApp() {...@@ -87,7 +89,7 @@ function startApp() {
87 ? electronArgs89 ? electronArgs
88 : [...electronArgs, `--t3code-dev-root=${desktopDir}`, "dist-electron/main.cjs"];90 : [...electronArgs, `--t3code-dev-root=${desktopDir}`, "dist-electron/main.cjs"];
89 const electronCommand = resolveElectronLaunchCommand(launchArgs);91 const electronCommand = resolveElectronLaunchCommand(launchArgs);
90 const app = spawn(electronCommand.electronPath, electronCommand.args, {92 const app = NodeChildProcess.spawn(electronCommand.electronPath, electronCommand.args, {
91 cwd: desktopDir,93 cwd: desktopDir,
92 env: childEnv,94 env: childEnv,
93 stdio: "inherit",95 stdio: "inherit",
...@@ -180,8 +182,8 @@ function scheduleRestart() {...@@ -180,8 +182,8 @@ function scheduleRestart() {
180182
181function startWatchers() {183function startWatchers() {
182 for (const { directory, files } of watchedDirectories) {184 for (const { directory, files } of watchedDirectories) {
183 const watcher = watch(185 const watcher = NodeFS.watch(
184 join(desktopDir, directory),186 NodePath.join(desktopDir, directory),
185 { persistent: true },187 { persistent: true },
186 (_eventType, filename) => {188 (_eventType, filename) => {
187 if (typeof filename !== "string" || !files.has(filename)) {189 if (typeof filename !== "string" || !files.has(filename)) {
...@@ -202,7 +204,9 @@ function killChildTree(signal) {...@@ -202,7 +204,9 @@ function killChildTree(signal) {
202 }204 }
203205
204 // Kill direct children as a final fallback in case normal shutdown leaves stragglers.206 // Kill direct children as a final fallback in case normal shutdown leaves stragglers.
205 spawnSync("pkill", [`-${signal}`, "-P", String(process.pid)], { stdio: "ignore" });207 NodeChildProcess.spawnSync("pkill", [`-${signal}`, "-P", String(process.pid)], {
208 stdio: "ignore",
209 });
206}210}
207211
208async function shutdown(exitCode) {212async function shutdown(exitCode) {
apps/desktop/scripts/electron-launcher.mjs+89-73
...@@ -1,29 +1,18 @@...@@ -1,29 +1,18 @@
1// This file mostly exists because we want dev mode to say "T3 Code (Dev)" instead of "electron"1// This file mostly exists because we want dev mode to say "T3 Code (Dev)" instead of "electron"
22
3import { spawnSync } from "node:child_process";3import * as NodeChildProcess from "node:child_process";
4import {4import * as NodeFS from "node:fs";
5 copyFileSync,5import * as NodeModule from "node:module";
6 chmodSync,
7 cpSync,
8 existsSync,
9 mkdirSync,
10 mkdtempSync,
11 readFileSync,
12 rmSync,
13 statSync,
14 writeFileSync,
15} from "node:fs";
16import { createRequire } from "node:module";
17import * as NodeOS from "node:os";6import * as NodeOS from "node:os";
18import { basename, dirname, join, resolve } from "node:path";7import * as NodePath from "node:path";
19import { fileURLToPath } from "node:url";8import * as NodeURL from "node:url";
20import { ensureElectronRuntime } from "./ensure-electron-runtime.mjs";9import { ensureElectronRuntime } from "./ensure-electron-runtime.mjs";
2110
22const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);11const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);
23const __dirname = dirname(fileURLToPath(import.meta.url));12const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
24export const desktopDir = resolve(__dirname, "..");13export const desktopDir = NodePath.resolve(__dirname, "..");
25const repoRoot = resolve(desktopDir, "..", "..");14const repoRoot = NodePath.resolve(desktopDir, "..", "..");
26const devBundleIdSuffix = basename(repoRoot)15const devBundleIdSuffix = NodePath.basename(repoRoot)
27 .toLowerCase()16 .toLowerCase()
28 .replaceAll(/[^a-z0-9]+/g, "");17 .replaceAll(/[^a-z0-9]+/g, "");
29export const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";18export const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
...@@ -32,22 +21,35 @@ export const APP_BUNDLE_ID = isDevelopment...@@ -32,22 +21,35 @@ export const APP_BUNDLE_ID = isDevelopment
32 : "com.t3tools.t3code";21 : "com.t3tools.t3code";
33const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];22const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];
34const LAUNCHER_VERSION = 12;23const LAUNCHER_VERSION = 12;
35const defaultIconPath = join(desktopDir, "resources", "icon.icns");24const defaultIconPath = NodePath.join(desktopDir, "resources", "icon.icns");
36const developmentMacIconPngPath = join(repoRoot, "assets", "dev", "blueprint-macos-1024.png");25const developmentMacIconPngPath = NodePath.join(
26 repoRoot,
27 "assets",
28 "dev",
29 "blueprint-macos-1024.png",
30);
37// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime.31// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime.
38const hostPlatform = NodeOS.platform();32const hostPlatform = NodeOS.platform();
3933
40function setPlistString(plistPath, key, value) {34function setPlistString(plistPath, key, value) {
41 const replaceResult = spawnSync("plutil", ["-replace", key, "-string", value, plistPath], {35 const replaceResult = NodeChildProcess.spawnSync(
42 encoding: "utf8",36 "plutil",
43 });37 ["-replace", key, "-string", value, plistPath],
38 {
39 encoding: "utf8",
40 },
41 );
44 if (replaceResult.status === 0) {42 if (replaceResult.status === 0) {
45 return;43 return;
46 }44 }
4745
48 const insertResult = spawnSync("plutil", ["-insert", key, "-string", value, plistPath], {46 const insertResult = NodeChildProcess.spawnSync(
49 encoding: "utf8",47 "plutil",
50 });48 ["-insert", key, "-string", value, plistPath],
49 {
50 encoding: "utf8",
51 },
52 );
51 if (insertResult.status === 0) {53 if (insertResult.status === 0) {
52 return;54 return;
53 }55 }
...@@ -58,16 +60,24 @@ function setPlistString(plistPath, key, value) {...@@ -58,16 +60,24 @@ function setPlistString(plistPath, key, value) {
5860
59function setPlistJson(plistPath, key, value) {61function setPlistJson(plistPath, key, value) {
60 const serialized = JSON.stringify(value);62 const serialized = JSON.stringify(value);
61 const replaceResult = spawnSync("plutil", ["-replace", key, "-json", serialized, plistPath], {63 const replaceResult = NodeChildProcess.spawnSync(
62 encoding: "utf8",64 "plutil",
63 });65 ["-replace", key, "-json", serialized, plistPath],
66 {
67 encoding: "utf8",
68 },
69 );
64 if (replaceResult.status === 0) {70 if (replaceResult.status === 0) {
65 return;71 return;
66 }72 }
6773
68 const insertResult = spawnSync("plutil", ["-insert", key, "-json", serialized, plistPath], {74 const insertResult = NodeChildProcess.spawnSync(
69 encoding: "utf8",75 "plutil",
70 });76 ["-insert", key, "-json", serialized, plistPath],
77 {
78 encoding: "utf8",
79 },
80 );
71 if (insertResult.status === 0) {81 if (insertResult.status === 0) {
72 return;82 return;
73 }83 }
...@@ -77,7 +87,7 @@ function setPlistJson(plistPath, key, value) {...@@ -77,7 +87,7 @@ function setPlistJson(plistPath, key, value) {
77}87}
7888
79function runChecked(command, args) {89function runChecked(command, args) {
80 const result = spawnSync(command, args, { encoding: "utf8" });90 const result = NodeChildProcess.spawnSync(command, args, { encoding: "utf8" });
81 if (result.status === 0) {91 if (result.status === 0) {
82 return;92 return;
83 }93 }
...@@ -91,7 +101,7 @@ function shellSingleQuote(value) {...@@ -91,7 +101,7 @@ function shellSingleQuote(value) {
91}101}
92102
93function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {103function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {
94 const mainEntryPath = join(desktopDir, "dist-electron", "main.cjs");104 const mainEntryPath = NodePath.join(desktopDir, "dist-electron", "main.cjs");
95 const envEntries = [105 const envEntries = [
96 ["VITE_DEV_SERVER_URL", process.env.VITE_DEV_SERVER_URL],106 ["VITE_DEV_SERVER_URL", process.env.VITE_DEV_SERVER_URL],
97 ["T3CODE_PORT", process.env.T3CODE_PORT],107 ["T3CODE_PORT", process.env.T3CODE_PORT],
...@@ -101,7 +111,7 @@ function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {...@@ -101,7 +111,7 @@ function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {
101 ["T3CODE_OTLP_EXPORT_INTERVAL_MS", process.env.T3CODE_OTLP_EXPORT_INTERVAL_MS],111 ["T3CODE_OTLP_EXPORT_INTERVAL_MS", process.env.T3CODE_OTLP_EXPORT_INTERVAL_MS],
102 ["T3CODE_DESKTOP_APP_USER_MODEL_ID", APP_BUNDLE_ID],112 ["T3CODE_DESKTOP_APP_USER_MODEL_ID", APP_BUNDLE_ID],
103 ].filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0);113 ].filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0);
104 writeFileSync(114 NodeFS.writeFileSync(
105 targetBinaryPath,115 targetBinaryPath,
106 [116 [
107 "#!/bin/sh",117 "#!/bin/sh",
...@@ -110,7 +120,7 @@ function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {...@@ -110,7 +120,7 @@ function writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath) {
110 "",120 "",
111 ].join("\n"),121 ].join("\n"),
112 );122 );
113 chmodSync(targetBinaryPath, 0o755);123 NodeFS.chmodSync(targetBinaryPath, 0o755);
114}124}
115125
116function registerMacLauncherBundle(appBundlePath) {126function registerMacLauncherBundle(appBundlePath) {
...@@ -140,21 +150,24 @@ function registerMacLauncherBundle(appBundlePath) {...@@ -140,21 +150,24 @@ function registerMacLauncherBundle(appBundlePath) {
140}150}
141151
142function ensureDevelopmentIconIcns(runtimeDir) {152function ensureDevelopmentIconIcns(runtimeDir) {
143 const generatedIconPath = join(runtimeDir, "icon-dev.icns");153 const generatedIconPath = NodePath.join(runtimeDir, "icon-dev.icns");
144 mkdirSync(runtimeDir, { recursive: true });154 NodeFS.mkdirSync(runtimeDir, { recursive: true });
145155
146 if (!existsSync(developmentMacIconPngPath)) {156 if (!NodeFS.existsSync(developmentMacIconPngPath)) {
147 return defaultIconPath;157 return defaultIconPath;
148 }158 }
149159
150 const sourceMtimeMs = statSync(developmentMacIconPngPath).mtimeMs;160 const sourceMtimeMs = NodeFS.statSync(developmentMacIconPngPath).mtimeMs;
151 if (existsSync(generatedIconPath) && statSync(generatedIconPath).mtimeMs >= sourceMtimeMs) {161 if (
162 NodeFS.existsSync(generatedIconPath) &&
163 NodeFS.statSync(generatedIconPath).mtimeMs >= sourceMtimeMs
164 ) {
152 return generatedIconPath;165 return generatedIconPath;
153 }166 }
154167
155 const iconsetRoot = mkdtempSync(join(runtimeDir, "dev-iconset-"));168 const iconsetRoot = NodeFS.mkdtempSync(NodePath.join(runtimeDir, "dev-iconset-"));
156 const iconsetDir = join(iconsetRoot, "icon.iconset");169 const iconsetDir = NodePath.join(iconsetRoot, "icon.iconset");
157 mkdirSync(iconsetDir, { recursive: true });170 NodeFS.mkdirSync(iconsetDir, { recursive: true });
158171
159 try {172 try {
160 for (const size of [16, 32, 128, 256, 512]) {173 for (const size of [16, 32, 128, 256, 512]) {
...@@ -164,7 +177,7 @@ function ensureDevelopmentIconIcns(runtimeDir) {...@@ -164,7 +177,7 @@ function ensureDevelopmentIconIcns(runtimeDir) {
164 String(size),177 String(size),
165 developmentMacIconPngPath,178 developmentMacIconPngPath,
166 "--out",179 "--out",
167 join(iconsetDir, `icon_${size}x${size}.png`),180 NodePath.join(iconsetDir, `icon_${size}x${size}.png`),
168 ]);181 ]);
169182
170 const retinaSize = size * 2;183 const retinaSize = size * 2;
...@@ -174,7 +187,7 @@ function ensureDevelopmentIconIcns(runtimeDir) {...@@ -174,7 +187,7 @@ function ensureDevelopmentIconIcns(runtimeDir) {
174 String(retinaSize),187 String(retinaSize),
175 developmentMacIconPngPath,188 developmentMacIconPngPath,
176 "--out",189 "--out",
177 join(iconsetDir, `icon_${size}x${size}@2x.png`),190 NodePath.join(iconsetDir, `icon_${size}x${size}@2x.png`),
178 ]);191 ]);
179 }192 }
180193
...@@ -187,12 +200,12 @@ function ensureDevelopmentIconIcns(runtimeDir) {...@@ -187,12 +200,12 @@ function ensureDevelopmentIconIcns(runtimeDir) {
187 );200 );
188 return defaultIconPath;201 return defaultIconPath;
189 } finally {202 } finally {
190 rmSync(iconsetRoot, { recursive: true, force: true });203 NodeFS.rmSync(iconsetRoot, { recursive: true, force: true });
191 }204 }
192}205}
193206
194function patchMainBundleInfoPlist(appBundlePath, iconPath) {207function patchMainBundleInfoPlist(appBundlePath, iconPath) {
195 const infoPlistPath = join(appBundlePath, "Contents", "Info.plist");208 const infoPlistPath = NodePath.join(appBundlePath, "Contents", "Info.plist");
196 setPlistString(infoPlistPath, "CFBundleDisplayName", APP_DISPLAY_NAME);209 setPlistString(infoPlistPath, "CFBundleDisplayName", APP_DISPLAY_NAME);
197 setPlistString(infoPlistPath, "CFBundleName", APP_DISPLAY_NAME);210 setPlistString(infoPlistPath, "CFBundleName", APP_DISPLAY_NAME);
198 setPlistString(infoPlistPath, "CFBundleIdentifier", APP_BUNDLE_ID);211 setPlistString(infoPlistPath, "CFBundleIdentifier", APP_BUNDLE_ID);
...@@ -204,9 +217,9 @@ function patchMainBundleInfoPlist(appBundlePath, iconPath) {...@@ -204,9 +217,9 @@ function patchMainBundleInfoPlist(appBundlePath, iconPath) {
204 },217 },
205 ]);218 ]);
206219
207 const resourcesDir = join(appBundlePath, "Contents", "Resources");220 const resourcesDir = NodePath.join(appBundlePath, "Contents", "Resources");
208 copyFileSync(iconPath, join(resourcesDir, "icon.icns"));221 NodeFS.copyFileSync(iconPath, NodePath.join(resourcesDir, "icon.icns"));
209 copyFileSync(iconPath, join(resourcesDir, "electron.icns"));222 NodeFS.copyFileSync(iconPath, NodePath.join(resourcesDir, "electron.icns"));
210}223}
211224
212function patchHelperBundleInfoPlists(appBundlePath) {225function patchHelperBundleInfoPlists(appBundlePath) {
...@@ -218,7 +231,7 @@ function patchHelperBundleInfoPlists(appBundlePath) {...@@ -218,7 +231,7 @@ function patchHelperBundleInfoPlists(appBundlePath) {
218 ];231 ];
219232
220 for (const [bundleName, bundleIdentifierSuffix, bundleDisplayName] of helperBundleNames) {233 for (const [bundleName, bundleIdentifierSuffix, bundleDisplayName] of helperBundleNames) {
221 const infoPlistPath = join(234 const infoPlistPath = NodePath.join(
222 appBundlePath,235 appBundlePath,
223 "Contents",236 "Contents",
224 "Frameworks",237 "Frameworks",
...@@ -226,7 +239,7 @@ function patchHelperBundleInfoPlists(appBundlePath) {...@@ -226,7 +239,7 @@ function patchHelperBundleInfoPlists(appBundlePath) {
226 "Contents",239 "Contents",
227 "Info.plist",240 "Info.plist",
228 );241 );
229 if (!existsSync(infoPlistPath)) {242 if (!NodeFS.existsSync(infoPlistPath)) {
230 continue;243 continue;
231 }244 }
232245
...@@ -242,34 +255,34 @@ function patchHelperBundleInfoPlists(appBundlePath) {...@@ -242,34 +255,34 @@ function patchHelperBundleInfoPlists(appBundlePath) {
242255
243function readJson(path) {256function readJson(path) {
244 try {257 try {
245 return JSON.parse(readFileSync(path, "utf8"));258 return JSON.parse(NodeFS.readFileSync(path, "utf8"));
246 } catch {259 } catch {
247 return null;260 return null;
248 }261 }
249}262}
250263
251function buildMacLauncher(electronBinaryPath) {264function buildMacLauncher(electronBinaryPath) {
252 const sourceAppBundlePath = resolve(dirname(electronBinaryPath), "../..");265 const sourceAppBundlePath = NodePath.resolve(NodePath.dirname(electronBinaryPath), "../..");
253 const runtimeDir = join(desktopDir, ".electron-runtime");266 const runtimeDir = NodePath.join(desktopDir, ".electron-runtime");
254 const targetAppBundlePath = join(runtimeDir, `${APP_DISPLAY_NAME}.app`);267 const targetAppBundlePath = NodePath.join(runtimeDir, `${APP_DISPLAY_NAME}.app`);
255 const targetBinaryPath = join(targetAppBundlePath, "Contents", "MacOS", "Electron");268 const targetBinaryPath = NodePath.join(targetAppBundlePath, "Contents", "MacOS", "Electron");
256 const iconPath = isDevelopment ? ensureDevelopmentIconIcns(runtimeDir) : defaultIconPath;269 const iconPath = isDevelopment ? ensureDevelopmentIconIcns(runtimeDir) : defaultIconPath;
257 const metadataPath = join(runtimeDir, "metadata.json");270 const metadataPath = NodePath.join(runtimeDir, "metadata.json");
258271
259 mkdirSync(runtimeDir, { recursive: true });272 NodeFS.mkdirSync(runtimeDir, { recursive: true });
260273
261 const expectedMetadata = {274 const expectedMetadata = {
262 launcherVersion: LAUNCHER_VERSION,275 launcherVersion: LAUNCHER_VERSION,
263 sourceAppBundlePath,276 sourceAppBundlePath,
264 sourceAppMtimeMs: statSync(sourceAppBundlePath).mtimeMs,277 sourceAppMtimeMs: NodeFS.statSync(sourceAppBundlePath).mtimeMs,
265 iconMtimeMs: statSync(iconPath).mtimeMs,278 iconMtimeMs: NodeFS.statSync(iconPath).mtimeMs,
266 appBundleId: APP_BUNDLE_ID,279 appBundleId: APP_BUNDLE_ID,
267 appProtocolSchemes: APP_PROTOCOL_SCHEMES,280 appProtocolSchemes: APP_PROTOCOL_SCHEMES,
268 };281 };
269282
270 const currentMetadata = readJson(metadataPath);283 const currentMetadata = readJson(metadataPath);
271 if (284 if (
272 existsSync(targetBinaryPath) &&285 NodeFS.existsSync(targetBinaryPath) &&
273 currentMetadata &&286 currentMetadata &&
274 JSON.stringify(currentMetadata) === JSON.stringify(expectedMetadata)287 JSON.stringify(currentMetadata) === JSON.stringify(expectedMetadata)
275 ) {288 ) {
...@@ -277,18 +290,21 @@ function buildMacLauncher(electronBinaryPath) {...@@ -277,18 +290,21 @@ function buildMacLauncher(electronBinaryPath) {
277 return targetBinaryPath;290 return targetBinaryPath;
278 }291 }
279292
280 rmSync(targetAppBundlePath, { recursive: true, force: true });293 NodeFS.rmSync(targetAppBundlePath, { recursive: true, force: true });
281 // verbatimSymlinks keeps the framework's relative symlinks intact294 // verbatimSymlinks keeps the framework's relative symlinks intact
282 // (e.g. Resources -> Versions/Current/Resources). Without it cpSync295 // (e.g. Resources -> Versions/Current/Resources). Without it cpSync
283 // rewrites them to absolute paths into node_modules, which escape the296 // rewrites them to absolute paths into node_modules, which escape the
284 // bundle and crash sandboxed helper processes (icudtl.dat not found).297 // bundle and crash sandboxed helper processes (icudtl.dat not found).
285 cpSync(sourceAppBundlePath, targetAppBundlePath, { recursive: true, verbatimSymlinks: true });298 NodeFS.cpSync(sourceAppBundlePath, targetAppBundlePath, {
299 recursive: true,
300 verbatimSymlinks: true,
301 });
286 patchMainBundleInfoPlist(targetAppBundlePath, iconPath);302 patchMainBundleInfoPlist(targetAppBundlePath, iconPath);
287 patchHelperBundleInfoPlists(targetAppBundlePath);303 patchHelperBundleInfoPlists(targetAppBundlePath);
288 if (isDevelopment) {304 if (isDevelopment) {
289 writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath);305 writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath);
290 }306 }
291 writeFileSync(metadataPath, `${JSON.stringify(expectedMetadata, null, 2)}\n`);307 NodeFS.writeFileSync(metadataPath, `${JSON.stringify(expectedMetadata, null, 2)}\n`);
292 registerMacLauncherBundle(targetAppBundlePath);308 registerMacLauncherBundle(targetAppBundlePath);
293309
294 return targetBinaryPath;310 return targetBinaryPath;
...@@ -299,9 +315,9 @@ function isLinuxSetuidSandboxConfigured(electronBinaryPath) {...@@ -299,9 +315,9 @@ function isLinuxSetuidSandboxConfigured(electronBinaryPath) {
299 return true;315 return true;
300 }316 }
301317
302 const sandboxPath = join(dirname(electronBinaryPath), "chrome-sandbox");318 const sandboxPath = NodePath.join(NodePath.dirname(electronBinaryPath), "chrome-sandbox");
303 try {319 try {
304 const sandboxStat = statSync(sandboxPath);320 const sandboxStat = NodeFS.statSync(sandboxPath);
305 return sandboxStat.uid === 0 && (sandboxStat.mode & 0o4777) === 0o4755;321 return sandboxStat.uid === 0 && (sandboxStat.mode & 0o4777) === 0o4755;
306 } catch {322 } catch {
307 return false;323 return false;
...@@ -322,7 +338,7 @@ function resolveLinuxSandboxArgs(electronBinaryPath) {...@@ -322,7 +338,7 @@ function resolveLinuxSandboxArgs(electronBinaryPath) {
322export function resolveElectronPath() {338export function resolveElectronPath() {
323 ensureElectronRuntime();339 ensureElectronRuntime();
324340
325 const require = createRequire(import.meta.url);341 const require = NodeModule.createRequire(import.meta.url);
326 const electronBinaryPath = require("electron");342 const electronBinaryPath = require("electron");
327343
328 if (hostPlatform !== "darwin") {344 if (hostPlatform !== "darwin") {
...@@ -345,11 +361,11 @@ export function resolveDevProtocolClient() {...@@ -345,11 +361,11 @@ export function resolveDevProtocolClient() {
345 return null;361 return null;
346 }362 }
347363
348 const require = createRequire(import.meta.url);364 const require = NodeModule.createRequire(import.meta.url);
349 const electronBinaryPath = require("electron");365 const electronBinaryPath = require("electron");
350 const launcherBinaryPath = buildMacLauncher(electronBinaryPath);366 const launcherBinaryPath = buildMacLauncher(electronBinaryPath);
351 return {367 return {
352 appBundlePath: resolve(launcherBinaryPath, "..", "..", ".."),368 appBundlePath: NodePath.resolve(launcherBinaryPath, "..", "..", ".."),
353 appBundleId: APP_BUNDLE_ID,369 appBundleId: APP_BUNDLE_ID,
354 };370 };
355}371}
apps/desktop/scripts/ensure-electron-runtime.mjs+34-32
...@@ -1,14 +1,14 @@...@@ -1,14 +1,14 @@
1import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";1import * as NodeFS from "node:fs";
2import { createRequire } from "node:module";2import * as NodeModule from "node:module";
3import { arch, platform, tmpdir } from "node:os";3import * as NodeOS from "node:os";
4import { dirname, join } from "node:path";4import * as NodePath from "node:path";
5import { spawnSync } from "node:child_process";5import * as NodeChildProcess from "node:child_process";
66
7const require = createRequire(import.meta.url);7const require = NodeModule.createRequire(import.meta.url);
8// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone repair script has no Effect runtime.8// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone repair script has no Effect runtime.
9const hostPlatform = platform();9const hostPlatform = NodeOS.platform();
10// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone repair script has no Effect runtime.10// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone repair script has no Effect runtime.
11const hostArch = arch();11const hostArch = NodeOS.arch();
1212
13function getPlatformPath() {13function getPlatformPath() {
14 switch (hostPlatform) {14 switch (hostPlatform) {
...@@ -27,26 +27,28 @@ function getPlatformPath() {...@@ -27,26 +27,28 @@ function getPlatformPath() {
2727
28function ensureExecutable(filePath) {28function ensureExecutable(filePath) {
29 if (hostPlatform !== "win32") {29 if (hostPlatform !== "win32") {
30 chmodSync(filePath, 0o755);30 NodeFS.chmodSync(filePath, 0o755);
31 }31 }
32}32}
3333
34function repairPathFile(electronDir, platformPath) {34function repairPathFile(electronDir, platformPath) {
35 const pathFile = join(electronDir, "path.txt");35 const pathFile = NodePath.join(electronDir, "path.txt");
36 const currentPath = existsSync(pathFile) ? readFileSync(pathFile, "utf8") : undefined;36 const currentPath = NodeFS.existsSync(pathFile)
37 ? NodeFS.readFileSync(pathFile, "utf8")
38 : undefined;
3739
38 if (currentPath !== platformPath) {40 if (currentPath !== platformPath) {
39 writeFileSync(pathFile, platformPath);41 NodeFS.writeFileSync(pathFile, platformPath);
40 }42 }
41}43}
4244
43function getRequiredRuntimePaths(electronDir, platformPath) {45function getRequiredRuntimePaths(electronDir, platformPath) {
44 const paths = [join(electronDir, "dist", platformPath)];46 const paths = [NodePath.join(electronDir, "dist", platformPath)];
4547
46 if (hostPlatform === "darwin") {48 if (hostPlatform === "darwin") {
47 paths.push(49 paths.push(
48 join(electronDir, "dist", "Electron.app", "Contents", "Info.plist"),50 NodePath.join(electronDir, "dist", "Electron.app", "Contents", "Info.plist"),
49 join(51 NodePath.join(
50 electronDir,52 electronDir,
51 "dist",53 "dist",
52 "Electron.app",54 "Electron.app",
...@@ -66,7 +68,7 @@ function isMachO(filePath) {...@@ -66,7 +68,7 @@ function isMachO(filePath) {
66 return true;68 return true;
67 }69 }
6870
69 const result = spawnSync("file", ["-b", filePath], {71 const result = NodeChildProcess.spawnSync("file", ["-b", filePath], {
70 encoding: "utf8",72 encoding: "utf8",
71 });73 });
7274
...@@ -75,7 +77,7 @@ function isMachO(filePath) {...@@ -75,7 +77,7 @@ function isMachO(filePath) {
7577
76function missingRuntimePaths(electronDir, platformPath) {78function missingRuntimePaths(electronDir, platformPath) {
77 return getRequiredRuntimePaths(electronDir, platformPath).filter((runtimePath) => {79 return getRequiredRuntimePaths(electronDir, platformPath).filter((runtimePath) => {
78 return !existsSync(runtimePath);80 return !NodeFS.existsSync(runtimePath);
79 });81 });
80}82}
8183
...@@ -85,8 +87,8 @@ function invalidRuntimePaths(electronDir, platformPath) {...@@ -85,8 +87,8 @@ function invalidRuntimePaths(electronDir, platformPath) {
85 }87 }
8688
87 return [89 return [
88 join(electronDir, "dist", platformPath),90 NodePath.join(electronDir, "dist", platformPath),
89 join(91 NodePath.join(
90 electronDir,92 electronDir,
91 "dist",93 "dist",
92 "Electron.app",94 "Electron.app",
...@@ -95,11 +97,11 @@ function invalidRuntimePaths(electronDir, platformPath) {...@@ -95,11 +97,11 @@ function invalidRuntimePaths(electronDir, platformPath) {
95 "Electron Framework.framework",97 "Electron Framework.framework",
96 "Electron Framework",98 "Electron Framework",
97 ),99 ),
98 ].filter((runtimePath) => existsSync(runtimePath) && !isMachO(runtimePath));100 ].filter((runtimePath) => NodeFS.existsSync(runtimePath) && !isMachO(runtimePath));
99}101}
100102
101function runChecked(command, args) {103function runChecked(command, args) {
102 const result = spawnSync(command, args, {104 const result = NodeChildProcess.spawnSync(command, args, {
103 encoding: "utf8",105 encoding: "utf8",
104 stdio: "inherit",106 stdio: "inherit",
105 });107 });
...@@ -114,8 +116,8 @@ function runChecked(command, args) {...@@ -114,8 +116,8 @@ function runChecked(command, args) {
114}116}
115117
116function installElectronRuntime(electronDir, version) {118function installElectronRuntime(electronDir, version) {
117 const tempDir = mkdtempSync(join(tmpdir(), "t3-electron-"));119 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-electron-"));
118 const zipPath = join(tempDir, `electron-v${version}-${hostPlatform}-${hostArch}.zip`);120 const zipPath = NodePath.join(tempDir, `electron-v${version}-${hostPlatform}-${hostArch}.zip`);
119121
120 try {122 try {
121 runChecked("curl", [123 runChecked("curl", [
...@@ -125,34 +127,34 @@ function installElectronRuntime(electronDir, version) {...@@ -125,34 +127,34 @@ function installElectronRuntime(electronDir, version) {
125 zipPath,127 zipPath,
126 ]);128 ]);
127 if (hostPlatform === "darwin") {129 if (hostPlatform === "darwin") {
128 runChecked("ditto", ["-x", "-k", zipPath, join(electronDir, "dist")]);130 runChecked("ditto", ["-x", "-k", zipPath, NodePath.join(electronDir, "dist")]);
129 } else {131 } else {
130 runChecked("python3", [132 runChecked("python3", [
131 "-c",133 "-c",
132 "import os, sys, zipfile; os.makedirs(sys.argv[2], exist_ok=True); zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])",134 "import os, sys, zipfile; os.makedirs(sys.argv[2], exist_ok=True); zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])",
133 zipPath,135 zipPath,
134 join(electronDir, "dist"),136 NodePath.join(electronDir, "dist"),
135 ]);137 ]);
136 }138 }
137 } finally {139 } finally {
138 rmSync(tempDir, { recursive: true, force: true });140 NodeFS.rmSync(tempDir, { recursive: true, force: true });
139 }141 }
140}142}
141143
142export function ensureElectronRuntime() {144export function ensureElectronRuntime() {
143 const electronPackageJsonPath = require.resolve("electron/package.json");145 const electronPackageJsonPath = require.resolve("electron/package.json");
144 const electronPackageJson = JSON.parse(readFileSync(electronPackageJsonPath, "utf8"));146 const electronPackageJson = JSON.parse(NodeFS.readFileSync(electronPackageJsonPath, "utf8"));
145 const electronDir = dirname(electronPackageJsonPath);147 const electronDir = NodePath.dirname(electronPackageJsonPath);
146 const platformPath = getPlatformPath();148 const platformPath = getPlatformPath();
147 const electronPath = join(electronDir, "dist", platformPath);149 const electronPath = NodePath.join(electronDir, "dist", platformPath);
148 const missingBeforeInstall = missingRuntimePaths(electronDir, platformPath);150 const missingBeforeInstall = missingRuntimePaths(electronDir, platformPath);
149 const invalidBeforeInstall = invalidRuntimePaths(electronDir, platformPath);151 const invalidBeforeInstall = invalidRuntimePaths(electronDir, platformPath);
150152
151 if (missingBeforeInstall.length > 0 || invalidBeforeInstall.length > 0) {153 if (missingBeforeInstall.length > 0 || invalidBeforeInstall.length > 0) {
152 if (existsSync(join(electronDir, "dist"))) {154 if (NodeFS.existsSync(NodePath.join(electronDir, "dist"))) {
153 rmSync(join(electronDir, "dist"), { recursive: true, force: true });155 NodeFS.rmSync(NodePath.join(electronDir, "dist"), { recursive: true, force: true });
154 }156 }
155 rmSync(join(electronDir, "path.txt"), { force: true });157 NodeFS.rmSync(NodePath.join(electronDir, "path.txt"), { force: true });
156 installElectronRuntime(electronDir, electronPackageJson.version);158 installElectronRuntime(electronDir, electronPackageJson.version);
157 }159 }
158160
apps/desktop/scripts/smoke-test.mjs+7-7
...@@ -1,16 +1,16 @@...@@ -1,16 +1,16 @@
1import { spawn } from "node:child_process";1import * as NodeChildProcess from "node:child_process";
2import { dirname, resolve } from "node:path";2import * as NodePath from "node:path";
3import { fileURLToPath } from "node:url";3import * as NodeURL from "node:url";
4import { resolveElectronLaunchCommand } from "./electron-launcher.mjs";4import { resolveElectronLaunchCommand } from "./electron-launcher.mjs";
55
6const __dirname = dirname(fileURLToPath(import.meta.url));6const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
7const desktopDir = resolve(__dirname, "..");7const desktopDir = NodePath.resolve(__dirname, "..");
8const mainJs = resolve(desktopDir, "dist-electron/main.cjs");8const mainJs = NodePath.resolve(desktopDir, "dist-electron/main.cjs");
99
10console.log("\nLaunching Electron smoke test...");10console.log("\nLaunching Electron smoke test...");
1111
12const electronCommand = resolveElectronLaunchCommand([mainJs]);12const electronCommand = resolveElectronLaunchCommand([mainJs]);
13const child = spawn(electronCommand.electronPath, electronCommand.args, {13const child = NodeChildProcess.spawn(electronCommand.electronPath, electronCommand.args, {
14 stdio: ["pipe", "pipe", "pipe"],14 stdio: ["pipe", "pipe", "pipe"],
15 env: {15 env: {
16 ...process.env,16 ...process.env,
apps/desktop/scripts/start-electron.mjs+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { spawn } from "node:child_process";1import * as NodeChildProcess from "node:child_process";
22
3import { desktopDir, resolveElectronLaunchCommand } from "./electron-launcher.mjs";3import { desktopDir, resolveElectronLaunchCommand } from "./electron-launcher.mjs";
44
...@@ -6,7 +6,7 @@ const childEnv = { ...process.env };...@@ -6,7 +6,7 @@ const childEnv = { ...process.env };
6delete childEnv.ELECTRON_RUN_AS_NODE;6delete childEnv.ELECTRON_RUN_AS_NODE;
77
8const electronCommand = resolveElectronLaunchCommand(["dist-electron/main.cjs"]);8const electronCommand = resolveElectronLaunchCommand(["dist-electron/main.cjs"]);
9const child = spawn(electronCommand.electronPath, electronCommand.args, {9const child = NodeChildProcess.spawn(electronCommand.electronPath, electronCommand.args, {
10 stdio: "inherit",10 stdio: "inherit",
11 cwd: desktopDir,11 cwd: desktopDir,
12 env: childEnv,12 env: childEnv,
apps/desktop/scripts/wait-for-resources.mjs+8-8
...@@ -1,13 +1,13 @@...@@ -1,13 +1,13 @@
1import * as FileSystem from "node:fs/promises";1import * as NodeFSP from "node:fs/promises";
2import * as Net from "node:net";2import * as NodeNet from "node:net";
3import * as Path from "node:path";3import * as NodePath from "node:path";
4import * as Timers from "node:timers/promises";4import * as NodeTimersPromises from "node:timers/promises";
55
6const defaultTcpHosts = ["127.0.0.1", "localhost", "::1"];6const defaultTcpHosts = ["127.0.0.1", "localhost", "::1"];
77
8async function fileExists(filePath) {8async function fileExists(filePath) {
9 try {9 try {
10 await FileSystem.access(filePath);10 await NodeFSP.access(filePath);
11 return true;11 return true;
12 } catch {12 } catch {
13 return false;13 return false;
...@@ -16,7 +16,7 @@ async function fileExists(filePath) {...@@ -16,7 +16,7 @@ async function fileExists(filePath) {
1616
17function tcpPortIsReady({ host, port, connectTimeoutMs = 500 }) {17function tcpPortIsReady({ host, port, connectTimeoutMs = 500 }) {
18 return new Promise((resolveReady) => {18 return new Promise((resolveReady) => {
19 const socket = Net.createConnection({ host, port });19 const socket = NodeNet.createConnection({ host, port });
20 let settled = false;20 let settled = false;
2121
22 const finish = (ready) => {22 const finish = (ready) => {
...@@ -47,7 +47,7 @@ async function resolvePendingResources({ baseDir, files, tcpPort, tcpHosts, conn...@@ -47,7 +47,7 @@ async function resolvePendingResources({ baseDir, files, tcpPort, tcpHosts, conn
47 const pendingFiles = [];47 const pendingFiles = [];
4848
49 for (const relativeFilePath of files) {49 for (const relativeFilePath of files) {
50 const ready = await fileExists(Path.resolve(baseDir, relativeFilePath));50 const ready = await fileExists(NodePath.resolve(baseDir, relativeFilePath));
51 if (!ready) {51 if (!ready) {
52 pendingFiles.push(relativeFilePath);52 pendingFiles.push(relativeFilePath);
53 }53 }
...@@ -114,6 +114,6 @@ export async function waitForResources({...@@ -114,6 +114,6 @@ export async function waitForResources({
114 );114 );
115 }115 }
116116
117 await Timers.setTimeout(intervalMs);117 await NodeTimersPromises.setTimeout(intervalMs);
118 }118 }
119}119}
apps/desktop/src/backend/DesktopNetworkInterfaces.ts+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { networkInterfaces } from "node:os";1import * as NodeOS from "node:os";
22
3import * as Context from "effect/Context";3import * as Context from "effect/Context";
4import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
...@@ -27,7 +27,7 @@ export class DesktopNetworkInterfaces extends Context.Service<...@@ -27,7 +27,7 @@ export class DesktopNetworkInterfaces extends Context.Service<
2727
28export const make = (): DesktopNetworkInterfaces["Service"] =>28export const make = (): DesktopNetworkInterfaces["Service"] =>
29 DesktopNetworkInterfaces.of({29 DesktopNetworkInterfaces.of({
30 read: Effect.sync(() => networkInterfaces()),30 read: Effect.sync(() => NodeOS.networkInterfaces()),
31 });31 });
3232
33export const layer = Layer.succeed(DesktopNetworkInterfaces, make());33export const layer = Layer.succeed(DesktopNetworkInterfaces, make());
apps/desktop/src/ipc/methods/preview.ts+2-2
...@@ -22,7 +22,7 @@ import {...@@ -22,7 +22,7 @@ import {
22import { BrowserWindow } from "electron";22import { BrowserWindow } from "electron";
23import * as Effect from "effect/Effect";23import * as Effect from "effect/Effect";
24import * as Schema from "effect/Schema";24import * as Schema from "effect/Schema";
25import { pathToFileURL } from "node:url";25import * as NodeURL from "node:url";
2626
27import * as PreviewManager from "../../preview/Manager.ts";27import * as PreviewManager from "../../preview/Manager.ts";
28import { PREVIEW_WEBVIEW_PREFERENCES } from "../../preview/WebviewPreferences.ts";28import { PREVIEW_WEBVIEW_PREFERENCES } from "../../preview/WebviewPreferences.ts";
...@@ -196,7 +196,7 @@ export const getPreviewConfig = DesktopIpc.makeIpcMethod({...@@ -196,7 +196,7 @@ export const getPreviewConfig = DesktopIpc.makeIpcMethod({
196 return {196 return {
197 partition: yield* manager.getBrowserPartition(environmentId),197 partition: yield* manager.getBrowserPartition(environmentId),
198 webPreferences: PREVIEW_WEBVIEW_PREFERENCES,198 webPreferences: PREVIEW_WEBVIEW_PREFERENCES,
199 preloadUrl: pathToFileURL(`${__dirname}/preview-pick-preload.cjs`).href,199 preloadUrl: NodeURL.pathToFileURL(`${__dirname}/preview-pick-preload.cjs`).href,
200 };200 };
201 }),201 }),
202});202});
apps/desktop/src/preview/PlaywrightInjectedRuntime.ts+11-7
...@@ -1,14 +1,14 @@...@@ -1,14 +1,14 @@
1// @effect-diagnostics nodeBuiltinImport:off - Extracts Playwright's installed Node bundle for browser injection.1// @effect-diagnostics nodeBuiltinImport:off - Extracts Playwright's installed Node bundle for browser injection.
2import { readFile } from "node:fs/promises";2import * as NodeFSP from "node:fs/promises";
3import { createRequire } from "node:module";3import * as NodeModule from "node:module";
4import { dirname, join } from "node:path";4import * as NodePath from "node:path";
5import { runInNewContext } from "node:vm";5import * as NodeVM from "node:vm";
66
7import * as Data from "effect/Data";7import * as Data from "effect/Data";
8import * as Effect from "effect/Effect";8import * as Effect from "effect/Effect";
9import * as Schema from "effect/Schema";9import * as Schema from "effect/Schema";
1010
11const require = createRequire(import.meta.url);11const require = NodeModule.createRequire(import.meta.url);
12const encodeUnknownJson = Schema.encodeUnknownEffect(Schema.UnknownFromJsonString);12const encodeUnknownJson = Schema.encodeUnknownEffect(Schema.UnknownFromJsonString);
1313
14export class PlaywrightInjectedRuntimeError extends Data.TaggedError(14export class PlaywrightInjectedRuntimeError extends Data.TaggedError(
...@@ -32,7 +32,11 @@ export const playwrightInjectedRuntimeSource = Effect.fn("PlaywrightInjectedRunt...@@ -32,7 +32,11 @@ export const playwrightInjectedRuntimeSource = Effect.fn("PlaywrightInjectedRunt
32 catch: (cause) => fail("resolvePackage", cause),32 catch: (cause) => fail("resolvePackage", cause),
33 });33 });
34 const coreBundle = yield* Effect.tryPromise({34 const coreBundle = yield* Effect.tryPromise({
35 try: () => readFile(join(dirname(packageJsonPath), "lib/coreBundle.js"), "utf8"),35 try: () =>
36 NodeFSP.readFile(
37 NodePath.join(NodePath.dirname(packageJsonPath), "lib/coreBundle.js"),
38 "utf8",
39 ),
36 catch: (cause) => fail("readCoreBundle", cause),40 catch: (cause) => fail("readCoreBundle", cause),
37 });41 });
38 const marker = "source3 = ";42 const marker = "source3 = ";
...@@ -53,7 +57,7 @@ export const playwrightInjectedRuntimeSource = Effect.fn("PlaywrightInjectedRunt...@@ -53,7 +57,7 @@ export const playwrightInjectedRuntimeSource = Effect.fn("PlaywrightInjectedRunt
53 }57 }
54 const literal = coreBundle.slice(literalStart, literalEnd);58 const literal = coreBundle.slice(literalStart, literalEnd);
55 const source = yield* Effect.try({59 const source = yield* Effect.try({
56 try: () => runInNewContext(literal, Object.create(null), { timeout: 1_000 }),60 try: () => NodeVM.runInNewContext(literal, Object.create(null), { timeout: 1_000 }),
57 catch: (cause) => fail("evaluateSourceLiteral", cause),61 catch: (cause) => fail("evaluateSourceLiteral", cause),
58 });62 });
59 if (typeof source !== "string" || source.length < 100_000) {63 if (typeof source !== "string" || source.length < 100_000) {
apps/mobile/modules/t3-markdown-text/scripts/sync-pierre-file-icons.mjs+21-18
...@@ -1,16 +1,19 @@...@@ -1,16 +1,19 @@
1import { execFileSync } from "node:child_process";1import * as NodeChildProcess from "node:child_process";
2import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";2import * as NodeFS from "node:fs";
3import { dirname, join, resolve } from "node:path";3import * as NodePath from "node:path";
4import { fileURLToPath } from "node:url";4import * as NodeURL from "node:url";
55
6import { getBuiltInSpriteSheet } from "@pierre/trees";6import { getBuiltInSpriteSheet } from "@pierre/trees";
77
8const scriptDirectory = dirname(fileURLToPath(import.meta.url));8const scriptDirectory = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
9const moduleDirectory = resolve(scriptDirectory, "..");9const moduleDirectory = NodePath.resolve(scriptDirectory, "..");
10const repositoryRoot = resolve(moduleDirectory, "../../../..");10const repositoryRoot = NodePath.resolve(moduleDirectory, "../../../..");
11const outputDirectory = join(moduleDirectory, "assets/file-icons");11const outputDirectory = NodePath.join(moduleDirectory, "assets/file-icons");
12const generatedModulePath = join(moduleDirectory, "src/markdownFileIcons.generated.ts");12const generatedModulePath = NodePath.join(moduleDirectory, "src/markdownFileIcons.generated.ts");
13const webIconSource = readFileSync(join(repositoryRoot, "apps/web/src/pierre-icons.ts"), "utf8");13const webIconSource = NodeFS.readFileSync(
14 NodePath.join(repositoryRoot, "apps/web/src/pierre-icons.ts"),
15 "utf8",
16);
14const customSprite = webIconSource.match(/const T3_FILE_ICON_SPRITE = `([\s\S]*?)`;/)?.[1];17const customSprite = webIconSource.match(/const T3_FILE_ICON_SPRITE = `([\s\S]*?)`;/)?.[1];
1518
16if (!customSprite) {19if (!customSprite) {
...@@ -95,20 +98,20 @@ function symbolFromSprite(sprite, id) {...@@ -95,20 +98,20 @@ function symbolFromSprite(sprite, id) {
95}98}
9699
97function renderIcon(token, symbol, color) {100function renderIcon(token, symbol, color) {
98 const svgPath = join(outputDirectory, `.pierre-${token}.svg`);101 const svgPath = NodePath.join(outputDirectory, `.pierre-${token}.svg`);
99 const pngPath = join(outputDirectory, `pierre_${token}.png`);102 const pngPath = NodePath.join(outputDirectory, `pierre_${token}.png`);
100 writeFileSync(103 NodeFS.writeFileSync(
101 svgPath,104 svgPath,
102 `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="${symbol.viewBox}" style="color:${color}">${symbol.body}</svg>`,105 `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="${symbol.viewBox}" style="color:${color}">${symbol.body}</svg>`,
103 );106 );
104 execFileSync("sips", ["-s", "format", "png", svgPath, "--out", pngPath], {107 NodeChildProcess.execFileSync("sips", ["-s", "format", "png", svgPath, "--out", pngPath], {
105 stdio: "ignore",108 stdio: "ignore",
106 });109 });
107 rmSync(svgPath);110 NodeFS.rmSync(svgPath);
108}111}
109112
110rmSync(outputDirectory, { recursive: true, force: true });113NodeFS.rmSync(outputDirectory, { recursive: true, force: true });
111mkdirSync(outputDirectory, { recursive: true });114NodeFS.mkdirSync(outputDirectory, { recursive: true });
112115
113const builtInSprite = getBuiltInSpriteSheet("complete");116const builtInSprite = getBuiltInSpriteSheet("complete");
114const builtInTokens = [...builtInSprite.matchAll(/<symbol id="file-tree-builtin-([^"]+)"/g)]117const builtInTokens = [...builtInSprite.matchAll(/<symbol id="file-tree-builtin-([^"]+)"/g)]
...@@ -130,4 +133,4 @@ const tokens = [...new Set([...builtInTokens, ...Object.keys(customIcons)])].sor...@@ -130,4 +133,4 @@ const tokens = [...new Set([...builtInTokens, ...Object.keys(customIcons)])].sor
130const generatedSource = `import type { ImageSourcePropType } from "react-native";\n\nexport const MARKDOWN_FILE_ICON_SOURCES = {\n${tokens133const generatedSource = `import type { ImageSourcePropType } from "react-native";\n\nexport const MARKDOWN_FILE_ICON_SOURCES = {\n${tokens
131 .map((token) => ` ${token}: require("../assets/file-icons/pierre_${token}.png"),`)134 .map((token) => ` ${token}: require("../assets/file-icons/pierre_${token}.png"),`)
132 .join("\n")}\n} as const satisfies Readonly<Record<string, ImageSourcePropType>>;\n`;135 .join("\n")}\n} as const satisfies Readonly<Record<string, ImageSourcePropType>>;\n`;
133writeFileSync(generatedModulePath, generatedSource);136NodeFS.writeFileSync(generatedModulePath, generatedSource);
apps/server/integration/OrchestrationEngineHarness.integration.ts+2-2
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { execFileSync } from "node:child_process";2import * as NodeChildProcess from "node:child_process";
33
4import * as NodeServices from "@effect/platform-node/NodeServices";4import * as NodeServices from "@effect/platform-node/NodeServices";
5import {5import {
...@@ -82,7 +82,7 @@ import * as AgentAwarenessRelay from "../src/relay/AgentAwarenessRelay.ts";...@@ -82,7 +82,7 @@ import * as AgentAwarenessRelay from "../src/relay/AgentAwarenessRelay.ts";
82const decodeCodexSettings = Schema.decodeEffect(CodexSettings);82const decodeCodexSettings = Schema.decodeEffect(CodexSettings);
8383
84function runGit(cwd: string, args: ReadonlyArray<string>) {84function runGit(cwd: string, args: ReadonlyArray<string>) {
85 return execFileSync("git", args, {85 return NodeChildProcess.execFileSync("git", args, {
86 cwd,86 cwd,
87 stdio: ["ignore", "pipe", "pipe"],87 stdio: ["ignore", "pipe", "pipe"],
88 encoding: "utf8",88 encoding: "utf8",
apps/server/integration/orchestrationEngine.integration.test.ts+12-9
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import path from "node:path";3import * as NodePath from "node:path";
44
5import {5import {
6 ApprovalRequestId,6 ApprovalRequestId,
...@@ -409,7 +409,7 @@ it.live("runs multi-turn file edits and persists checkpoint diffs", () =>...@@ -409,7 +409,7 @@ it.live("runs multi-turn file edits and persists checkpoint diffs", () =>
409 ],409 ],
410 mutateWorkspace: ({ cwd }) =>410 mutateWorkspace: ({ cwd }) =>
411 Effect.sync(() => {411 Effect.sync(() => {
412 fs.writeFileSync(path.join(cwd, "README.md"), "v2\n", "utf8");412 NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), "v2\n", "utf8");
413 }),413 }),
414 });414 });
415415
...@@ -456,7 +456,7 @@ it.live("runs multi-turn file edits and persists checkpoint diffs", () =>...@@ -456,7 +456,7 @@ it.live("runs multi-turn file edits and persists checkpoint diffs", () =>
456 ],456 ],
457 mutateWorkspace: ({ cwd }) =>457 mutateWorkspace: ({ cwd }) =>
458 Effect.sync(() => {458 Effect.sync(() => {
459 fs.writeFileSync(path.join(cwd, "README.md"), "v3\n", "utf8");459 NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), "v3\n", "utf8");
460 }),460 }),
461 });461 });
462462
...@@ -752,7 +752,7 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git...@@ -752,7 +752,7 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git
752 ],752 ],
753 mutateWorkspace: ({ cwd }) =>753 mutateWorkspace: ({ cwd }) =>
754 Effect.sync(() => {754 Effect.sync(() => {
755 fs.writeFileSync(path.join(cwd, "README.md"), "v2\n", "utf8");755 NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), "v2\n", "utf8");
756 }),756 }),
757 });757 });
758 yield* startTurn({758 yield* startTurn({
...@@ -811,7 +811,7 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git...@@ -811,7 +811,7 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git
811 ],811 ],
812 mutateWorkspace: ({ cwd }) =>812 mutateWorkspace: ({ cwd }) =>
813 Effect.sync(() => {813 Effect.sync(() => {
814 fs.writeFileSync(path.join(cwd, "README.md"), "v3\n", "utf8");814 NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), "v3\n", "utf8");
815 }),815 }),
816 });816 });
817 yield* startTurn({817 yield* startTurn({
...@@ -869,7 +869,10 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git...@@ -869,7 +869,10 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git
869 ),869 ),
870 true,870 true,
871 );871 );
872 assert.equal(fs.readFileSync(path.join(harness.workspaceDir, "README.md"), "utf8"), "v2\n");872 assert.equal(
873 NodeFS.readFileSync(NodePath.join(harness.workspaceDir, "README.md"), "utf8"),
874 "v2\n",
875 );
873 assert.equal(876 assert.equal(
874 gitRefExists(harness.workspaceDir, checkpointRefForThreadTurn(THREAD_ID, 2)),877 gitRefExists(harness.workspaceDir, checkpointRefForThreadTurn(THREAD_ID, 2)),
875 false,878 false,
...@@ -1332,7 +1335,7 @@ it.live("reverts claudeAgent turns and rolls back provider conversation state",...@@ -1332,7 +1335,7 @@ it.live("reverts claudeAgent turns and rolls back provider conversation state",
1332 ],1335 ],
1333 mutateWorkspace: ({ cwd }) =>1336 mutateWorkspace: ({ cwd }) =>
1334 Effect.sync(() => {1337 Effect.sync(() => {
1335 fs.writeFileSync(path.join(cwd, "README.md"), "v2\n", "utf8");1338 NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), "v2\n", "utf8");
1336 }),1339 }),
1337 });1340 });
13381341
...@@ -1390,7 +1393,7 @@ it.live("reverts claudeAgent turns and rolls back provider conversation state",...@@ -1390,7 +1393,7 @@ it.live("reverts claudeAgent turns and rolls back provider conversation state",
1390 ],1393 ],
1391 mutateWorkspace: ({ cwd }) =>1394 mutateWorkspace: ({ cwd }) =>
1392 Effect.sync(() => {1395 Effect.sync(() => {
1393 fs.writeFileSync(path.join(cwd, "README.md"), "v3\n", "utf8");1396 NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), "v3\n", "utf8");
1394 }),1397 }),
1395 });1398 });
13961399
apps/server/scripts/acp-mock-agent.ts+3-3
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1#!/usr/bin/env node1#!/usr/bin/env node
2// @effect-diagnostics nodeBuiltinImport:off2// @effect-diagnostics nodeBuiltinImport:off
3import { appendFileSync } from "node:fs";3import * as NodeFS from "node:fs";
44
5import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
66
...@@ -42,7 +42,7 @@ function logExit(reason: string): void {...@@ -42,7 +42,7 @@ function logExit(reason: string): void {
42 if (!exitLogPath) {42 if (!exitLogPath) {
43 return;43 return;
44 }44 }
45 appendFileSync(exitLogPath, `${reason}\n`, "utf8");45 NodeFS.appendFileSync(exitLogPath, `${reason}\n`, "utf8");
46}46}
4747
48process.once("SIGTERM", () => {48process.once("SIGTERM", () => {
...@@ -693,7 +693,7 @@ const program = Effect.gen(function* () {...@@ -693,7 +693,7 @@ const program = Effect.gen(function* () {
693 }693 }
694 const payload = event.payload;694 const payload = event.payload;
695 return Effect.sync(() => {695 return Effect.sync(() => {
696 appendFileSync(696 NodeFS.appendFileSync(
697 requestLogPath,697 requestLogPath,
698 payload.endsWith("\n") ? payload : `${payload}\n`,698 payload.endsWith("\n") ? payload : `${payload}\n`,
699 "utf8",699 "utf8",
apps/server/scripts/cursor-acp-model-mismatch-probe.ts+24-24
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";2import * as NodeChildProcess from "node:child_process";
3import process from "node:process";3import * as NodeProcess from "node:process";
4import readline from "node:readline";4import * as NodeReadline from "node:readline";
5import * as NodeTimers from "node:timers";5import * as NodeTimers from "node:timers";
6import { resolveSpawnCommand } from "@t3tools/shared/shell";6import { resolveSpawnCommand } from "@t3tools/shared/shell";
7import * as Effect from "effect/Effect";7import * as Effect from "effect/Effect";
...@@ -56,19 +56,19 @@ type PendingRequest = {...@@ -56,19 +56,19 @@ type PendingRequest = {
56 reject: (error: Error) => void;56 reject: (error: Error) => void;
57};57};
5858
59const targetCwd = process.argv[2] ?? process.cwd();59const targetCwd = NodeProcess.argv[2] ?? NodeProcess.cwd();
60const targetModel = process.argv[3] ?? "gpt-5.4";60const targetModel = NodeProcess.argv[3] ?? "gpt-5.4";
61const promptText = process.argv[4] ?? "helo";61const promptText = NodeProcess.argv[4] ?? "helo";
62const targetReasoning = process.env.CURSOR_REASONING ?? "";62const targetReasoning = NodeProcess.env.CURSOR_REASONING ?? "";
63const targetContext = process.env.CURSOR_CONTEXT ?? "";63const targetContext = NodeProcess.env.CURSOR_CONTEXT ?? "";
64const targetFast = process.env.CURSOR_FAST ?? "";64const targetFast = NodeProcess.env.CURSOR_FAST ?? "";
65const agentBin = process.env.CURSOR_AGENT_BIN ?? "agent";65const agentBin = NodeProcess.env.CURSOR_AGENT_BIN ?? "agent";
66const promptWaitMs = Number(process.env.CURSOR_PROMPT_WAIT_MS ?? "4000");66const promptWaitMs = Number(NodeProcess.env.CURSOR_PROMPT_WAIT_MS ?? "4000");
67const requestTimeoutMs = Number(process.env.CURSOR_REQUEST_TIMEOUT_MS ?? "20000");67const requestTimeoutMs = Number(NodeProcess.env.CURSOR_REQUEST_TIMEOUT_MS ?? "20000");
6868
69function logSection(title: string, value: unknown) {69function logSection(title: string, value: unknown) {
70 process.stdout.write(`\n=== ${title} ===\n`);70 NodeProcess.stdout.write(`\n=== ${title} ===\n`);
71 process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);71 NodeProcess.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
72}72}
7373
74function fail(message: string): never {74function fail(message: string): never {
...@@ -124,18 +124,18 @@ function sleep(ms: number) {...@@ -124,18 +124,18 @@ function sleep(ms: number) {
124}124}
125125
126class JsonRpcChild {126class JsonRpcChild {
127 readonly child: ChildProcessWithoutNullStreams;127 readonly child: NodeChildProcess.ChildProcessWithoutNullStreams;
128 readonly pending = new Map<JsonRpcId, PendingRequest>();128 readonly pending = new Map<JsonRpcId, PendingRequest>();
129 nextId = 1;129 nextId = 1;
130 closed = false;130 closed = false;
131131
132 constructor(bin: string, args: string[], cwd: string) {132 constructor(bin: string, args: string[], cwd: string) {
133 const spawnCommand = Effect.runSync(resolveSpawnCommand(bin, args));133 const spawnCommand = Effect.runSync(resolveSpawnCommand(bin, args));
134 this.child = spawn(spawnCommand.command, spawnCommand.args, {134 this.child = NodeChildProcess.spawn(spawnCommand.command, spawnCommand.args, {
135 cwd,135 cwd,
136 shell: spawnCommand.shell,136 shell: spawnCommand.shell,
137 stdio: ["pipe", "pipe", "pipe"],137 stdio: ["pipe", "pipe", "pipe"],
138 env: process.env,138 env: NodeProcess.env,
139 });139 });
140140
141 this.child.on("exit", (code, signal) => {141 this.child.on("exit", (code, signal) => {
...@@ -155,14 +155,14 @@ class JsonRpcChild {...@@ -155,14 +155,14 @@ class JsonRpcChild {
155 this.pending.clear();155 this.pending.clear();
156 });156 });
157157
158 const stdout = readline.createInterface({ input: this.child.stdout });158 const stdout = NodeReadline.createInterface({ input: this.child.stdout });
159 stdout.on("line", (line) => {159 stdout.on("line", (line) => {
160 void this.handleStdoutLine(line);160 void this.handleStdoutLine(line);
161 });161 });
162162
163 const stderr = readline.createInterface({ input: this.child.stderr });163 const stderr = NodeReadline.createInterface({ input: this.child.stderr });
164 stderr.on("line", (line) => {164 stderr.on("line", (line) => {
165 process.stdout.write(`[stderr] ${line}\n`);165 NodeProcess.stdout.write(`[stderr] ${line}\n`);
166 });166 });
167 }167 }
168168
...@@ -175,7 +175,7 @@ class JsonRpcChild {...@@ -175,7 +175,7 @@ class JsonRpcChild {
175 headers: [],175 headers: [],
176 ...message,176 ...message,
177 });177 });
178 process.stdout.write(`>>> ${payload}\n`);178 NodeProcess.stdout.write(`>>> ${payload}\n`);
179 this.child.stdin.write(`${payload}\n`);179 this.child.stdin.write(`${payload}\n`);
180 }180 }
181181
...@@ -240,13 +240,13 @@ class JsonRpcChild {...@@ -240,13 +240,13 @@ class JsonRpcChild {
240 return;240 return;
241 }241 }
242242
243 process.stdout.write(`<<< ${line}\n`);243 NodeProcess.stdout.write(`<<< ${line}\n`);
244244
245 let message: JsonRpcMessage;245 let message: JsonRpcMessage;
246 try {246 try {
247 message = JSON.parse(line) as JsonRpcMessage;247 message = JSON.parse(line) as JsonRpcMessage;
248 } catch (error) {248 } catch (error) {
249 process.stdout.write(`[parse-error] ${(error as Error).message}\n`);249 NodeProcess.stdout.write(`[parse-error] ${(error as Error).message}\n`);
250 return;250 return;
251 }251 }
252252
...@@ -435,7 +435,7 @@ async function main() {...@@ -435,7 +435,7 @@ async function main() {
435}435}
436436
437void main().catch((error: unknown) => {437void main().catch((error: unknown) => {
438 process.stderr.write(438 NodeProcess.stderr.write(
439 `${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`,439 `${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`,
440 );440 );
441 process.exitCode = 1;441 process.exitCode = 1;
apps/server/src/attachmentPaths.ts+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import NodePath from "node:path";2import * as NodePath from "node:path";
33
4export function normalizeAttachmentRelativePath(rawRelativePath: string): string | null {4export function normalizeAttachmentRelativePath(rawRelativePath: string): string | null {
5 const normalized = NodePath.normalize(rawRelativePath).replace(/^[/\\]+/, "");5 const normalized = NodePath.normalize(rawRelativePath).replace(/^[/\\]+/, "");
apps/server/src/attachmentStore.test.ts+13-9
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import os from "node:os";3import * as NodeOS from "node:os";
4import path from "node:path";4import * as NodePath from "node:path";
55
6import { describe, expect, it } from "vite-plus/test";6import { describe, expect, it } from "vite-plus/test";
77
...@@ -45,11 +45,13 @@ describe("attachmentStore", () => {...@@ -45,11 +45,13 @@ describe("attachmentStore", () => {
45 });45 });
4646
47 it("resolves attachment path by id using the extension that exists on disk", () => {47 it("resolves attachment path by id using the extension that exists on disk", () => {
48 const attachmentsDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3code-attachment-store-"));48 const attachmentsDir = NodeFS.mkdtempSync(
49 NodePath.join(NodeOS.tmpdir(), "t3code-attachment-store-"),
50 );
49 try {51 try {
50 const attachmentId = "thread-1-attachment";52 const attachmentId = "thread-1-attachment";
51 const pngPath = path.join(attachmentsDir, `${attachmentId}.png`);53 const pngPath = NodePath.join(attachmentsDir, `${attachmentId}.png`);
52 fs.writeFileSync(pngPath, Buffer.from("hello"));54 NodeFS.writeFileSync(pngPath, Buffer.from("hello"));
5355
54 const resolved = resolveAttachmentPathById({56 const resolved = resolveAttachmentPathById({
55 attachmentsDir,57 attachmentsDir,
...@@ -57,12 +59,14 @@ describe("attachmentStore", () => {...@@ -57,12 +59,14 @@ describe("attachmentStore", () => {
57 });59 });
58 expect(resolved).toBe(pngPath);60 expect(resolved).toBe(pngPath);
59 } finally {61 } finally {
60 fs.rmSync(attachmentsDir, { recursive: true, force: true });62 NodeFS.rmSync(attachmentsDir, { recursive: true, force: true });
61 }63 }
62 });64 });
6365
64 it("returns null when no attachment file exists for the id", () => {66 it("returns null when no attachment file exists for the id", () => {
65 const attachmentsDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3code-attachment-store-"));67 const attachmentsDir = NodeFS.mkdtempSync(
68 NodePath.join(NodeOS.tmpdir(), "t3code-attachment-store-"),
69 );
66 try {70 try {
67 const resolved = resolveAttachmentPathById({71 const resolved = resolveAttachmentPathById({
68 attachmentsDir,72 attachmentsDir,
...@@ -70,7 +74,7 @@ describe("attachmentStore", () => {...@@ -70,7 +74,7 @@ describe("attachmentStore", () => {
70 });74 });
71 expect(resolved).toBeNull();75 expect(resolved).toBeNull();
72 } finally {76 } finally {
73 fs.rmSync(attachmentsDir, { recursive: true, force: true });77 NodeFS.rmSync(attachmentsDir, { recursive: true, force: true });
74 }78 }
75 });79 });
76});80});
apps/server/src/attachmentStore.ts+4-4
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { randomUUID } from "node:crypto";2import * as NodeCrypto from "node:crypto";
3import { existsSync } from "node:fs";3import * as NodeFS from "node:fs";
44
5import type { ChatAttachment } from "@t3tools/contracts";5import type { ChatAttachment } from "@t3tools/contracts";
66
...@@ -39,7 +39,7 @@ export function createAttachmentId(threadId: string): string | null {...@@ -39,7 +39,7 @@ export function createAttachmentId(threadId: string): string | null {
39 if (!threadSegment) {39 if (!threadSegment) {
40 return null;40 return null;
41 }41 }
42 return `${threadSegment}-${randomUUID()}`;42 return `${threadSegment}-${NodeCrypto.randomUUID()}`;
43}43}
4444
45export function parseThreadSegmentFromAttachmentId(attachmentId: string): string | null {45export function parseThreadSegmentFromAttachmentId(attachmentId: string): string | null {
...@@ -89,7 +89,7 @@ export function resolveAttachmentPathById(input: {...@@ -89,7 +89,7 @@ export function resolveAttachmentPathById(input: {
89 attachmentsDir: input.attachmentsDir,89 attachmentsDir: input.attachmentsDir,
90 relativePath: `${normalizedId}${extension}`,90 relativePath: `${normalizedId}${extension}`,
91 });91 });
92 if (maybePath && existsSync(maybePath)) {92 if (maybePath && NodeFS.existsSync(maybePath)) {
93 return maybePath;93 return maybePath;
94 }94 }
95 }95 }
apps/server/src/auth/utils.ts+3-3
...@@ -4,7 +4,7 @@ import type {...@@ -4,7 +4,7 @@ import type {
4 AuthClientPresentationMetadata,4 AuthClientPresentationMetadata,
5} from "@t3tools/contracts";5} from "@t3tools/contracts";
6import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";6import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
7import * as Crypto from "node:crypto";7import * as NodeCrypto from "node:crypto";
8import * as Encoding from "effect/Encoding";8import * as Encoding from "effect/Encoding";
9import * as Result from "effect/Result";9import * as Result from "effect/Result";
1010
...@@ -32,7 +32,7 @@ export function base64UrlDecodeUtf8(input: string): string {...@@ -32,7 +32,7 @@ export function base64UrlDecodeUtf8(input: string): string {
32}32}
3333
34export function signPayload(payload: string, secret: Uint8Array): string {34export function signPayload(payload: string, secret: Uint8Array): string {
35 return Crypto.createHmac("sha256", Buffer.from(secret)).update(payload).digest("base64url");35 return NodeCrypto.createHmac("sha256", Buffer.from(secret)).update(payload).digest("base64url");
36}36}
3737
38export function timingSafeEqualBase64Url(left: string, right: string): boolean {38export function timingSafeEqualBase64Url(left: string, right: string): boolean {
...@@ -41,7 +41,7 @@ export function timingSafeEqualBase64Url(left: string, right: string): boolean {...@@ -41,7 +41,7 @@ export function timingSafeEqualBase64Url(left: string, right: string): boolean {
41 if (leftBuffer.length !== rightBuffer.length) {41 if (leftBuffer.length !== rightBuffer.length) {
42 return false;42 return false;
43 }43 }
44 return Crypto.timingSafeEqual(leftBuffer, rightBuffer);44 return NodeCrypto.timingSafeEqual(leftBuffer, rightBuffer);
45}45}
4646
47function normalizeNonEmptyString(value: string | null | undefined): string | undefined {47function normalizeNonEmptyString(value: string | null | undefined): string | undefined {
apps/server/src/bin.test.ts+47-25
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off - CLI integration exercises Node HTTP and filesystem boundaries.1// @effect-diagnostics nodeBuiltinImport:off - CLI integration exercises Node HTTP and filesystem boundaries.
2import { createServer } from "node:http";2import * as NodeHttp from "node:http";
3import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";3import * as NodeFS from "node:fs";
4import { tmpdir } from "node:os";4import * as NodeOS from "node:os";
5import { join } from "node:path";5import * as NodePath from "node:path";
66
7import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";7import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
8import * as NodeServices from "@effect/platform-node/NodeServices";8import * as NodeServices from "@effect/platform-node/NodeServices";
...@@ -124,7 +124,7 @@ const withLiveProjectCliServer = <A, E, R>(baseDir: string, run: () => Effect.Ef...@@ -124,7 +124,7 @@ const withLiveProjectCliServer = <A, E, R>(baseDir: string, run: () => Effect.Ef
124 ),124 ),
125 Layer.provideMerge(makeProjectPersistenceLayer(config)),125 Layer.provideMerge(makeProjectPersistenceLayer(config)),
126 Layer.provideMerge(126 Layer.provideMerge(
127 NodeHttpServer.layer(createServer, {127 NodeHttpServer.layer(NodeHttp.createServer, {
128 host: "127.0.0.1",128 host: "127.0.0.1",
129 port: 0,129 port: 0,
130 }),130 }),
...@@ -197,7 +197,9 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {...@@ -197,7 +197,9 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {
197197
198 it.effect("reports fresh headless connect state without requiring local configuration", () =>198 it.effect("reports fresh headless connect state without requiring local configuration", () =>
199 Effect.gen(function* () {199 Effect.gen(function* () {
200 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-cloud-status-test-"));200 const baseDir = NodeFS.mkdtempSync(
201 NodePath.join(NodeOS.tmpdir(), "t3-cli-cloud-status-test-"),
202 );
201 const { output } = yield* captureStdout(203 const { output } = yield* captureStdout(
202 runConnectCli(["connect", "status", "--base-dir", baseDir, "--json"]),204 runConnectCli(["connect", "status", "--base-dir", baseDir, "--json"]),
203 );205 );
...@@ -220,7 +222,9 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {...@@ -220,7 +222,9 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {
220222
221 it.effect("reports actionable human-readable headless connect state", () =>223 it.effect("reports actionable human-readable headless connect state", () =>
222 Effect.gen(function* () {224 Effect.gen(function* () {
223 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-cloud-status-human-test-"));225 const baseDir = NodeFS.mkdtempSync(
226 NodePath.join(NodeOS.tmpdir(), "t3-cli-cloud-status-human-test-"),
227 );
224 const { output } = yield* captureStdout(228 const { output } = yield* captureStdout(
225 runConnectCli(["connect", "status", "--base-dir", baseDir]),229 runConnectCli(["connect", "status", "--base-dir", baseDir]),
226 );230 );
...@@ -234,11 +238,13 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {...@@ -234,11 +238,13 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {
234238
235 it.effect("logs in to headless connect without enabling access", () =>239 it.effect("logs in to headless connect without enabling access", () =>
236 Effect.gen(function* () {240 Effect.gen(function* () {
237 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-cloud-login-test-"));241 const baseDir = NodeFS.mkdtempSync(
242 NodePath.join(NodeOS.tmpdir(), "t3-cli-cloud-login-test-"),
243 );
238 const { secretsDir } = yield* ServerConfig.deriveServerPaths(baseDir, undefined);244 const { secretsDir } = yield* ServerConfig.deriveServerPaths(baseDir, undefined);
239 mkdirSync(secretsDir, { recursive: true });245 NodeFS.mkdirSync(secretsDir, { recursive: true });
240 writeFileSync(246 NodeFS.writeFileSync(
241 join(secretsDir, "cloud-cli-oauth-token.bin"),247 NodePath.join(secretsDir, "cloud-cli-oauth-token.bin"),
242 // @effect-diagnostics-next-line preferSchemaOverJson:off - Test fixture matches the persisted CLI token representation.248 // @effect-diagnostics-next-line preferSchemaOverJson:off - Test fixture matches the persisted CLI token representation.
243 JSON.stringify({249 JSON.stringify({
244 accessToken: "access-token",250 accessToken: "access-token",
...@@ -267,7 +273,9 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {...@@ -267,7 +273,9 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {
267273
268 it.effect("disables headless connect without a running server", () =>274 it.effect("disables headless connect without a running server", () =>
269 Effect.gen(function* () {275 Effect.gen(function* () {
270 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-cloud-unlink-test-"));276 const baseDir = NodeFS.mkdtempSync(
277 NodePath.join(NodeOS.tmpdir(), "t3-cli-cloud-unlink-test-"),
278 );
271 const { output } = yield* captureStdout(279 const { output } = yield* captureStdout(
272 runConnectCli(["connect", "unlink", "--base-dir", baseDir]),280 runConnectCli(["connect", "unlink", "--base-dir", baseDir]),
273 );281 );
...@@ -278,24 +286,28 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {...@@ -278,24 +286,28 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {
278286
279 it.effect("logs out of headless connect and removes the stored CLI authorization", () =>287 it.effect("logs out of headless connect and removes the stored CLI authorization", () =>
280 Effect.gen(function* () {288 Effect.gen(function* () {
281 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-cloud-logout-test-"));289 const baseDir = NodeFS.mkdtempSync(
290 NodePath.join(NodeOS.tmpdir(), "t3-cli-cloud-logout-test-"),
291 );
282 const { secretsDir } = yield* ServerConfig.deriveServerPaths(baseDir, undefined);292 const { secretsDir } = yield* ServerConfig.deriveServerPaths(baseDir, undefined);
283 const tokenPath = join(secretsDir, "cloud-cli-oauth-token.bin");293 const tokenPath = NodePath.join(secretsDir, "cloud-cli-oauth-token.bin");
284 mkdirSync(secretsDir, { recursive: true });294 NodeFS.mkdirSync(secretsDir, { recursive: true });
285 writeFileSync(tokenPath, "invalid persisted token");295 NodeFS.writeFileSync(tokenPath, "invalid persisted token");
286296
287 const { output } = yield* captureStdout(297 const { output } = yield* captureStdout(
288 runConnectCli(["connect", "logout", "--base-dir", baseDir]),298 runConnectCli(["connect", "logout", "--base-dir", baseDir]),
289 );299 );
290300
291 assert.equal(output, "Signed out of T3 Connect locally.");301 assert.equal(output, "Signed out of T3 Connect locally.");
292 assert.isFalse(existsSync(tokenPath));302 assert.isFalse(NodeFS.existsSync(tokenPath));
293 }),303 }),
294 );304 );
295305
296 it.effect("executes auth pairing subcommands and redacts secrets from list output", () =>306 it.effect("executes auth pairing subcommands and redacts secrets from list output", () =>
297 Effect.gen(function* () {307 Effect.gen(function* () {
298 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-auth-pairing-test-"));308 const baseDir = NodeFS.mkdtempSync(
309 NodePath.join(NodeOS.tmpdir(), "t3-cli-auth-pairing-test-"),
310 );
299311
300 const createdOutput = yield* captureStdout(312 const createdOutput = yield* captureStdout(
301 runCli(["auth", "pairing", "create", "--base-dir", baseDir, "--json"]),313 runCli(["auth", "pairing", "create", "--base-dir", baseDir, "--json"]),
...@@ -325,7 +337,9 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {...@@ -325,7 +337,9 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {
325337
326 it.effect("executes auth session subcommands and redacts secrets from list output", () =>338 it.effect("executes auth session subcommands and redacts secrets from list output", () =>
327 Effect.gen(function* () {339 Effect.gen(function* () {
328 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-auth-session-test-"));340 const baseDir = NodeFS.mkdtempSync(
341 NodePath.join(NodeOS.tmpdir(), "t3-cli-auth-session-test-"),
342 );
329343
330 const issuedOutput = yield* captureStdout(344 const issuedOutput = yield* captureStdout(
331 runCli(["auth", "session", "issue", "--base-dir", baseDir, "--json"]),345 runCli(["auth", "session", "issue", "--base-dir", baseDir, "--json"]),
...@@ -400,8 +414,12 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {...@@ -400,8 +414,12 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {
400414
401 it.effect("adds, renames, and removes projects offline through the orchestration engine", () =>415 it.effect("adds, renames, and removes projects offline through the orchestration engine", () =>
402 Effect.gen(function* () {416 Effect.gen(function* () {
403 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-projects-offline-test-"));417 const baseDir = NodeFS.mkdtempSync(
404 const workspaceRoot = mkdtempSync(join(tmpdir(), "t3-cli-projects-workspace-"));418 NodePath.join(NodeOS.tmpdir(), "t3-cli-projects-offline-test-"),
419 );
420 const workspaceRoot = NodeFS.mkdtempSync(
421 NodePath.join(NodeOS.tmpdir(), "t3-cli-projects-workspace-"),
422 );
405423
406 yield* runCliWithRuntime([424 yield* runCliWithRuntime([
407 "project",425 "project",
...@@ -444,8 +462,12 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {...@@ -444,8 +462,12 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {
444462
445 it.effect("routes project commands through a running server when runtime state is present", () =>463 it.effect("routes project commands through a running server when runtime state is present", () =>
446 Effect.gen(function* () {464 Effect.gen(function* () {
447 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-projects-live-test-"));465 const baseDir = NodeFS.mkdtempSync(
448 const workspaceRoot = mkdtempSync(join(tmpdir(), "t3-cli-projects-live-workspace-"));466 NodePath.join(NodeOS.tmpdir(), "t3-cli-projects-live-test-"),
467 );
468 const workspaceRoot = NodeFS.mkdtempSync(
469 NodePath.join(NodeOS.tmpdir(), "t3-cli-projects-live-workspace-"),
470 );
449471
450 yield* withLiveProjectCliServer(baseDir, () =>472 yield* withLiveProjectCliServer(baseDir, () =>
451 Effect.gen(function* () {473 Effect.gen(function* () {
...@@ -472,8 +494,8 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {...@@ -472,8 +494,8 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => {
472494
473 it.effect("rejects dev-url on project commands", () =>495 it.effect("rejects dev-url on project commands", () =>
474 Effect.gen(function* () {496 Effect.gen(function* () {
475 const workspaceRoot = mkdtempSync(497 const workspaceRoot = NodeFS.mkdtempSync(
476 join(tmpdir(), "t3-cli-projects-unknown-option-workspace-"),498 NodePath.join(NodeOS.tmpdir(), "t3-cli-projects-unknown-option-workspace-"),
477 );499 );
478 const error = yield* runCliWithRuntime([500 const error = yield* runCliWithRuntime([
479 "project",501 "project",
apps/server/src/bootstrap.test.ts+13-13
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import * as NFS from "node:fs";2import * as NodeFS from "node:fs";
3import * as path from "node:path";3import * as NodePath from "node:path";
4import { execFileSync, spawn } from "node:child_process";4import * as NodeChildProcess from "node:child_process";
5import * as NodeServices from "@effect/platform-node/NodeServices";5import * as NodeServices from "@effect/platform-node/NodeServices";
6import { it } from "@effect/vitest";6import { it } from "@effect/vitest";
7import * as FileSystem from "effect/FileSystem";7import * as FileSystem from "effect/FileSystem";
...@@ -53,8 +53,8 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {...@@ -53,8 +53,8 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {
53 );53 );
5454
55 const fd = yield* Effect.acquireRelease(55 const fd = yield* Effect.acquireRelease(
56 Effect.sync(() => NFS.openSync(filePath, "r")),56 Effect.sync(() => NodeFS.openSync(filePath, "r")),
57 (fd) => Effect.sync(() => NFS.closeSync(fd)),57 (fd) => Effect.sync(() => NodeFS.closeSync(fd)),
58 );58 );
5959
60 const payload = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, { timeoutMs: 100 });60 const payload = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, { timeoutMs: 100 });
...@@ -78,7 +78,7 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {...@@ -78,7 +78,7 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {
78 // so the stream owns the fd lifecycle and closes it asynchronously on end.78 // so the stream owns the fd lifecycle and closes it asynchronously on end.
79 // Attempting to also close it synchronously in a finalizer races with the79 // Attempting to also close it synchronously in a finalizer races with the
80 // stream's async close and produces an uncaught EBADF.80 // stream's async close and produces an uncaught EBADF.
81 const fd = NFS.openSync(filePath, "r");81 const fd = NodeFS.openSync(filePath, "r");
8282
83 openSyncInterceptor.failPath = `/proc/self/fd/${fd}`;83 openSyncInterceptor.failPath = `/proc/self/fd/${fd}`;
84 try {84 try {
...@@ -96,8 +96,8 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {...@@ -96,8 +96,8 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {
9696
97 it.effect("returns none when the fd is unavailable", () =>97 it.effect("returns none when the fd is unavailable", () =>
98 Effect.gen(function* () {98 Effect.gen(function* () {
99 const fd = NFS.openSync("/dev/null", "r");99 const fd = NodeFS.openSync("/dev/null", "r");
100 NFS.closeSync(fd);100 NodeFS.closeSync(fd);
101101
102 const payload = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, { timeoutMs: 100 });102 const payload = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, { timeoutMs: 100 });
103 assertNone(payload);103 assertNone(payload);
...@@ -108,13 +108,13 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {...@@ -108,13 +108,13 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {
108 Effect.gen(function* () {108 Effect.gen(function* () {
109 const fs = yield* FileSystem.FileSystem;109 const fs = yield* FileSystem.FileSystem;
110 const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-bootstrap-" });110 const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-bootstrap-" });
111 const fifoPath = path.join(tempDir, "bootstrap.pipe");111 const fifoPath = NodePath.join(tempDir, "bootstrap.pipe");
112112
113 yield* Effect.sync(() => execFileSync("mkfifo", [fifoPath]));113 yield* Effect.sync(() => NodeChildProcess.execFileSync("mkfifo", [fifoPath]));
114114
115 const _writer = yield* Effect.acquireRelease(115 const _writer = yield* Effect.acquireRelease(
116 Effect.sync(() =>116 Effect.sync(() =>
117 spawn("sh", ["-c", 'exec 3>"$1"; sleep 60', "sh", fifoPath], {117 NodeChildProcess.spawn("sh", ["-c", 'exec 3>"$1"; sleep 60', "sh", fifoPath], {
118 stdio: ["ignore", "ignore", "ignore"],118 stdio: ["ignore", "ignore", "ignore"],
119 }),119 }),
120 ),120 ),
...@@ -125,8 +125,8 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {...@@ -125,8 +125,8 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => {
125 );125 );
126126
127 const fd = yield* Effect.acquireRelease(127 const fd = yield* Effect.acquireRelease(
128 Effect.sync(() => NFS.openSync(fifoPath, "r")),128 Effect.sync(() => NodeFS.openSync(fifoPath, "r")),
129 (fd) => Effect.sync(() => NFS.closeSync(fd)),129 (fd) => Effect.sync(() => NodeFS.closeSync(fd)),
130 );130 );
131131
132 const fiber = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, {132 const fiber = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, {
apps/server/src/bootstrap.ts+13-13
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import * as NFS from "node:fs";2import * as NodeFS from "node:fs";
3import * as Net from "node:net";3import * as NodeNet from "node:net";
4import * as readline from "node:readline";4import * as NodeReadline from "node:readline";
5import type { Readable } from "node:stream";5import type * as NodeStream from "node:stream";
66
7import * as Data from "effect/Data";7import * as Data from "effect/Data";
8import * as Effect from "effect/Effect";8import * as Effect from "effect/Effect";
...@@ -33,7 +33,7 @@ export const readBootstrapEnvelope = Effect.fn("readBootstrapEnvelope")(function...@@ -33,7 +33,7 @@ export const readBootstrapEnvelope = Effect.fn("readBootstrapEnvelope")(function
33 const timeoutMs = options?.timeoutMs ?? 1000;33 const timeoutMs = options?.timeoutMs ?? 1000;
3434
35 return yield* Effect.callback<Option.Option<A>, BootstrapError>((resume) => {35 return yield* Effect.callback<Option.Option<A>, BootstrapError>((resume) => {
36 const input = readline.createInterface({36 const input = NodeReadline.createInterface({
37 input: stream,37 input: stream,
38 crlfDelay: Infinity,38 crlfDelay: Infinity,
39 });39 });
...@@ -96,7 +96,7 @@ const isUnavailableBootstrapFdError = Predicate.compose(...@@ -96,7 +96,7 @@ const isUnavailableBootstrapFdError = Predicate.compose(
9696
97const isFdReady = (fd: number) =>97const isFdReady = (fd: number) =>
98 Effect.try({98 Effect.try({
99 try: () => NFS.fstatSync(fd),99 try: () => NodeFS.fstatSync(fd),
100 catch: (error) =>100 catch: (error) =>
101 new BootstrapError({101 new BootstrapError({
102 message: "Failed to stat bootstrap fd.",102 message: "Failed to stat bootstrap fd.",
...@@ -113,7 +113,7 @@ const isFdReady = (fd: number) =>...@@ -113,7 +113,7 @@ const isFdReady = (fd: number) =>
113const makeBootstrapInputStream = (fd: number) =>113const makeBootstrapInputStream = (fd: number) =>
114 Effect.gen(function* () {114 Effect.gen(function* () {
115 const platform = yield* HostProcessPlatform;115 const platform = yield* HostProcessPlatform;
116 return yield* Effect.try<Readable, BootstrapError>({116 return yield* Effect.try<NodeStream.Readable, BootstrapError>({
117 try: () => {117 try: () => {
118 const fdPath = resolveFdPath(fd, platform);118 const fdPath = resolveFdPath(fd, platform);
119 if (fdPath === undefined) {119 if (fdPath === undefined) {
...@@ -122,8 +122,8 @@ const makeBootstrapInputStream = (fd: number) =>...@@ -122,8 +122,8 @@ const makeBootstrapInputStream = (fd: number) =>
122122
123 let streamFd: number | undefined;123 let streamFd: number | undefined;
124 try {124 try {
125 streamFd = NFS.openSync(fdPath, "r");125 streamFd = NodeFS.openSync(fdPath, "r");
126 return NFS.createReadStream("", {126 return NodeFS.createReadStream("", {
127 fd: streamFd,127 fd: streamFd,
128 encoding: "utf8",128 encoding: "utf8",
129 autoClose: true,129 autoClose: true,
...@@ -131,7 +131,7 @@ const makeBootstrapInputStream = (fd: number) =>...@@ -131,7 +131,7 @@ const makeBootstrapInputStream = (fd: number) =>
131 } catch (error) {131 } catch (error) {
132 if (isBootstrapFdPathDuplicationError(error)) {132 if (isBootstrapFdPathDuplicationError(error)) {
133 if (streamFd !== undefined) {133 if (streamFd !== undefined) {
134 NFS.closeSync(streamFd);134 NodeFS.closeSync(streamFd);
135 }135 }
136 return makeDirectBootstrapStream(fd);136 return makeDirectBootstrapStream(fd);
137 }137 }
...@@ -146,15 +146,15 @@ const makeBootstrapInputStream = (fd: number) =>...@@ -146,15 +146,15 @@ const makeBootstrapInputStream = (fd: number) =>
146 });146 });
147 });147 });
148148
149const makeDirectBootstrapStream = (fd: number): Readable => {149const makeDirectBootstrapStream = (fd: number): NodeStream.Readable => {
150 try {150 try {
151 return NFS.createReadStream("", {151 return NodeFS.createReadStream("", {
152 fd,152 fd,
153 encoding: "utf8",153 encoding: "utf8",
154 autoClose: true,154 autoClose: true,
155 });155 });
156 } catch {156 } catch {
157 const stream = new Net.Socket({157 const stream = new NodeNet.Socket({
158 fd,158 fd,
159 readable: true,159 readable: true,
160 writable: false,160 writable: false,
apps/server/src/checkpointing/CheckpointStore.test.ts+4-4
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import path from "node:path";2import * as NodePath from "node:path";
33
4import * as NodeServices from "@effect/platform-node/NodeServices";4import * as NodeServices from "@effect/platform-node/NodeServices";
5import { it } from "@effect/vitest";5import { it } from "@effect/vitest";
...@@ -80,7 +80,7 @@ function initRepoWithCommit(...@@ -80,7 +80,7 @@ function initRepoWithCommit(
80 yield* git(cwd, ["init"]);80 yield* git(cwd, ["init"]);
81 yield* git(cwd, ["config", "user.email", "test@test.com"]);81 yield* git(cwd, ["config", "user.email", "test@test.com"]);
82 yield* git(cwd, ["config", "user.name", "Test"]);82 yield* git(cwd, ["config", "user.name", "Test"]);
83 yield* writeTextFile(path.join(cwd, "README.md"), "# test\n");83 yield* writeTextFile(NodePath.join(cwd, "README.md"), "# test\n");
84 yield* git(cwd, ["add", "."]);84 yield* git(cwd, ["add", "."]);
85 yield* git(cwd, ["commit", "-m", "initial commit"]);85 yield* git(cwd, ["commit", "-m", "initial commit"]);
86 });86 });
...@@ -107,7 +107,7 @@ it.layer(TestLayer)("CheckpointStore.layer", (it) => {...@@ -107,7 +107,7 @@ it.layer(TestLayer)("CheckpointStore.layer", (it) => {
107 cwd: tmp,107 cwd: tmp,
108 checkpointRef: fromCheckpointRef,108 checkpointRef: fromCheckpointRef,
109 });109 });
110 yield* writeTextFile(path.join(tmp, "README.md"), buildLargeText());110 yield* writeTextFile(NodePath.join(tmp, "README.md"), buildLargeText());
111 yield* checkpointStore.captureCheckpoint({111 yield* checkpointStore.captureCheckpoint({
112 cwd: tmp,112 cwd: tmp,
113 checkpointRef: toCheckpointRef,113 checkpointRef: toCheckpointRef,
...@@ -135,7 +135,7 @@ it.layer(TestLayer)("CheckpointStore.layer", (it) => {...@@ -135,7 +135,7 @@ it.layer(TestLayer)("CheckpointStore.layer", (it) => {
135 const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0);135 const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0);
136 const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1);136 const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1);
137137
138 const componentPath = path.join(tmp, "Component.tsx");138 const componentPath = NodePath.join(tmp, "Component.tsx");
139 yield* writeTextFile(139 yield* writeTextFile(
140 componentPath,140 componentPath,
141 [141 [
apps/server/src/cloud/CliTokenManager.ts+2-2
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1// @effect-diagnostics nodeBuiltinImport:off - The CLI loopback OAuth callback is a Node HTTP boundary.1// @effect-diagnostics nodeBuiltinImport:off - The CLI loopback OAuth callback is a Node HTTP boundary.
2import { createServer } from "node:http";2import * as NodeHttp from "node:http";
33
4import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";4import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
5import * as Clock from "effect/Clock";5import * as Clock from "effect/Clock";
...@@ -206,7 +206,7 @@ export const make = Effect.gen(function* () {...@@ -206,7 +206,7 @@ export const make = Effect.gen(function* () {
206 disableLogger: true,206 disableLogger: true,
207 }).pipe(207 }).pipe(
208 Layer.provide(208 Layer.provide(
209 NodeHttpServer.layer(createServer, {209 NodeHttpServer.layer(NodeHttp.createServer, {
210 host: "127.0.0.1",210 host: "127.0.0.1",
211 port: 34338,211 port: 34338,
212 disablePreemptiveShutdown: true,212 disablePreemptiveShutdown: true,
apps/server/src/cloud/http.ts+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { createPublicKey } from "node:crypto";1import * as NodeCrypto from "node:crypto";
2import {2import {
3 AuthRelayReadScope,3 AuthRelayReadScope,
4 AuthRelayWriteScope,4 AuthRelayWriteScope,
...@@ -152,7 +152,7 @@ function validateCloudMintPublicKey(...@@ -152,7 +152,7 @@ function validateCloudMintPublicKey(
152 publicKey: string,152 publicKey: string,
153): Effect.Effect<void, EnvironmentHttpBadRequestError> {153): Effect.Effect<void, EnvironmentHttpBadRequestError> {
154 return Effect.try({154 return Effect.try({
155 try: () => createPublicKey(publicKey.replace(/\\n/g, "\n")),155 try: () => NodeCrypto.createPublicKey(publicKey.replace(/\\n/g, "\n")),
156 catch: () =>156 catch: () =>
157 new EnvironmentHttpBadRequestError({157 new EnvironmentHttpBadRequestError({
158 message: "Cloud mint public key must be a valid Ed25519 public key.",158 message: "Cloud mint public key must be a valid Ed25519 public key.",
apps/server/src/environment/ServerEnvironment.test.ts+2-2
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { dirname } from "node:path";2import * as NodePath from "node:path";
3import * as NodeServices from "@effect/platform-node/NodeServices";3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { expect, it } from "@effect/vitest";4import { expect, it } from "@effect/vitest";
5import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
...@@ -76,7 +76,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {...@@ -76,7 +76,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
76 });76 });
77 const serverConfig = yield* makeServerConfig(baseDir);77 const serverConfig = yield* makeServerConfig(baseDir);
78 const environmentIdPath = serverConfig.environmentIdPath;78 const environmentIdPath = serverConfig.environmentIdPath;
79 yield* fileSystem.makeDirectory(dirname(environmentIdPath), { recursive: true });79 yield* fileSystem.makeDirectory(NodePath.dirname(environmentIdPath), { recursive: true });
80 yield* fileSystem.writeFileString(environmentIdPath, "persisted-environment-id\n");80 yield* fileSystem.writeFileString(environmentIdPath, "persisted-environment-id\n");
81 const writeAttempts: string[] = [];81 const writeAttempts: string[] = [];
82 const failingFileSystemLayer = FileSystem.layerNoop({82 const failingFileSystemLayer = FileSystem.layerNoop({
apps/server/src/git/GitManager.test.ts+64-56
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import path from "node:path";3import * as NodePath from "node:path";
4import { spawnSync } from "node:child_process";4import * as NodeChildProcess from "node:child_process";
55
6import * as NodeServices from "@effect/platform-node/NodeServices";6import * as NodeServices from "@effect/platform-node/NodeServices";
7import { it } from "@effect/vitest";7import { it } from "@effect/vitest";
...@@ -164,7 +164,7 @@ function normalizeFakePullRequestSummary(raw: unknown): GitHubCli.GitHubPullRequ...@@ -164,7 +164,7 @@ function normalizeFakePullRequestSummary(raw: unknown): GitHubCli.GitHubPullRequ
164}164}
165165
166function runGitSyncForFakeGh(cwd: string, args: readonly string[]): void {166function runGitSyncForFakeGh(cwd: string, args: readonly string[]): void {
167 const result = spawnSync("git", args, {167 const result = NodeChildProcess.spawnSync("git", args, {
168 cwd,168 cwd,
169 encoding: "utf8",169 encoding: "utf8",
170 });170 });
...@@ -254,7 +254,7 @@ function initRepo(...@@ -254,7 +254,7 @@ function initRepo(
254 yield* runGit(cwd, ["init", "--initial-branch=main"]);254 yield* runGit(cwd, ["init", "--initial-branch=main"]);
255 yield* runGit(cwd, ["config", "user.email", "test@example.com"]);255 yield* runGit(cwd, ["config", "user.email", "test@example.com"]);
256 yield* runGit(cwd, ["config", "user.name", "Test User"]);256 yield* runGit(cwd, ["config", "user.name", "Test User"]);
257 yield* fs.writeFileString(path.join(cwd, "README.md"), "hello\n");257 yield* fs.writeFileString(NodePath.join(cwd, "README.md"), "hello\n");
258 yield* runGit(cwd, ["add", "README.md"]);258 yield* runGit(cwd, ["add", "README.md"]);
259 yield* runGit(cwd, ["commit", "-m", "Initial commit"]);259 yield* runGit(cwd, ["commit", "-m", "Initial commit"]);
260 });260 });
...@@ -459,7 +459,7 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): {...@@ -459,7 +459,7 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): {
459 try: () => {459 try: () => {
460 const headBranch = scenario.pullRequest?.headRefName;460 const headBranch = scenario.pullRequest?.headRefName;
461 if (headBranch) {461 if (headBranch) {
462 const existingBranch = spawnSync(462 const existingBranch = NodeChildProcess.spawnSync(
463 "git",463 "git",
464 ["show-ref", "--verify", "--quiet", `refs/heads/${headBranch}`],464 ["show-ref", "--verify", "--quiet", `refs/heads/${headBranch}`],
465 {465 {
...@@ -916,7 +916,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -916,7 +916,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
916 it.effect("status returns an explicit non-repo result for deleted directories", () =>916 it.effect("status returns an explicit non-repo result for deleted directories", () =>
917 Effect.gen(function* () {917 Effect.gen(function* () {
918 const rootDir = yield* makeTempDir("t3code-git-manager-missing-dir-");918 const rootDir = yield* makeTempDir("t3code-git-manager-missing-dir-");
919 const cwd = path.join(rootDir, "deleted-repo");919 const cwd = NodePath.join(rootDir, "deleted-repo");
920 yield* makeDirectory(cwd);920 yield* makeDirectory(cwd);
921 yield* removePath(cwd);921 yield* removePath(cwd);
922 const { manager } = yield* makeManager();922 const { manager } = yield* makeManager();
...@@ -1026,7 +1026,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1026,7 +1026,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1026 const forkDir = yield* createBareRemote();1026 const forkDir = yield* createBareRemote();
1027 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);1027 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
1028 yield* runGit(repoDir, ["checkout", "-b", "statemachine"]);1028 yield* runGit(repoDir, ["checkout", "-b", "statemachine"]);
1029 fs.writeFileSync(path.join(repoDir, "fork-pr.txt"), "fork pr\n");1029 NodeFS.writeFileSync(NodePath.join(repoDir, "fork-pr.txt"), "fork pr\n");
1030 yield* runGit(repoDir, ["add", "fork-pr.txt"]);1030 yield* runGit(repoDir, ["add", "fork-pr.txt"]);
1031 yield* runGit(repoDir, ["commit", "-m", "Fork PR branch"]);1031 yield* runGit(repoDir, ["commit", "-m", "Fork PR branch"]);
1032 yield* runGit(repoDir, ["push", "-u", "fork-seed", "statemachine"]);1032 yield* runGit(repoDir, ["push", "-u", "fork-seed", "statemachine"]);
...@@ -1348,7 +1348,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1348,7 +1348,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1348 Effect.gen(function* () {1348 Effect.gen(function* () {
1349 const repoDir = yield* makeTempDir("t3code-git-manager-");1349 const repoDir = yield* makeTempDir("t3code-git-manager-");
1350 yield* initRepo(repoDir);1350 yield* initRepo(repoDir);
1351 fs.writeFileSync(path.join(repoDir, "README.md"), "hello\nworld\n");1351 NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\nworld\n");
13521352
1353 const { manager } = yield* makeManager();1353 const { manager } = yield* makeManager();
1354 const result = yield* runStackedAction(manager, {1354 const result = yield* runStackedAction(manager, {
...@@ -1383,7 +1383,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1383,7 +1383,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1383 Effect.gen(function* () {1383 Effect.gen(function* () {
1384 const repoDir = yield* makeTempDir("t3code-git-manager-");1384 const repoDir = yield* makeTempDir("t3code-git-manager-");
1385 yield* initRepo(repoDir);1385 yield* initRepo(repoDir);
1386 fs.writeFileSync(path.join(repoDir, "README.md"), "hello\ncustom\n");1386 NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\ncustom\n");
1387 let generatedCount = 0;1387 let generatedCount = 0;
13881388
1389 const { manager } = yield* makeManager({1389 const { manager } = yield* makeManager({
...@@ -1426,8 +1426,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1426,8 +1426,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1426 Effect.gen(function* () {1426 Effect.gen(function* () {
1427 const repoDir = yield* makeTempDir("t3code-git-manager-");1427 const repoDir = yield* makeTempDir("t3code-git-manager-");
1428 yield* initRepo(repoDir);1428 yield* initRepo(repoDir);
1429 fs.writeFileSync(path.join(repoDir, "a.txt"), "file a\n");1429 NodeFS.writeFileSync(NodePath.join(repoDir, "a.txt"), "file a\n");
1430 fs.writeFileSync(path.join(repoDir, "b.txt"), "file b\n");1430 NodeFS.writeFileSync(NodePath.join(repoDir, "b.txt"), "file b\n");
14311431
1432 const { manager } = yield* makeManager();1432 const { manager } = yield* makeManager();
1433 const result = yield* runStackedAction(manager, {1433 const result = yield* runStackedAction(manager, {
...@@ -1454,7 +1454,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1454,7 +1454,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1454 const remoteDir = yield* createBareRemote();1454 const remoteDir = yield* createBareRemote();
1455 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);1455 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1456 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);1456 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
1457 fs.writeFileSync(path.join(repoDir, "README.md"), "hello\nfeature-branch\n");1457 NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\nfeature-branch\n");
1458 let generatedCount = 0;1458 let generatedCount = 0;
14591459
1460 const { manager } = yield* makeManager({1460 const { manager } = yield* makeManager({
...@@ -1514,7 +1514,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1514,7 +1514,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1514 Effect.gen(function* () {1514 Effect.gen(function* () {
1515 const repoDir = yield* makeTempDir("t3code-git-manager-");1515 const repoDir = yield* makeTempDir("t3code-git-manager-");
1516 yield* initRepo(repoDir);1516 yield* initRepo(repoDir);
1517 fs.writeFileSync(path.join(repoDir, "README.md"), "hello\ncustom-feature\n");1517 NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\ncustom-feature\n");
1518 let generatedCount = 0;1518 let generatedCount = 0;
15191519
1520 const { manager } = yield* makeManager({1520 const { manager } = yield* makeManager({
...@@ -1597,7 +1597,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1597,7 +1597,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1597 yield* runGit(repoDir, ["checkout", "-b", "feature/stacked-flow"]);1597 yield* runGit(repoDir, ["checkout", "-b", "feature/stacked-flow"]);
1598 const remoteDir = yield* createBareRemote();1598 const remoteDir = yield* createBareRemote();
1599 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);1599 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1600 fs.writeFileSync(path.join(repoDir, "feature.txt"), "feature\n");1600 NodeFS.writeFileSync(NodePath.join(repoDir, "feature.txt"), "feature\n");
16011601
1602 const { manager } = yield* makeManager();1602 const { manager } = yield* makeManager();
1603 const result = yield* runStackedAction(manager, {1603 const result = yield* runStackedAction(manager, {
...@@ -1626,7 +1626,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1626,7 +1626,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1626 yield* runGit(repoDir, ["checkout", "-b", "feature/no-upstream-pr"]);1626 yield* runGit(repoDir, ["checkout", "-b", "feature/no-upstream-pr"]);
1627 const remoteDir = yield* createBareRemote();1627 const remoteDir = yield* createBareRemote();
1628 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);1628 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1629 fs.writeFileSync(path.join(repoDir, "feature.txt"), "feature\n");1629 NodeFS.writeFileSync(NodePath.join(repoDir, "feature.txt"), "feature\n");
16301630
1631 const { manager, ghCalls } = yield* makeManager({1631 const { manager, ghCalls } = yield* makeManager({
1632 ghScenario: {1632 ghScenario: {
...@@ -1697,7 +1697,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1697,7 +1697,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1697 yield* runGit(repoDir, ["checkout", "-b", "feature/push-only"]);1697 yield* runGit(repoDir, ["checkout", "-b", "feature/push-only"]);
1698 const remoteDir = yield* createBareRemote();1698 const remoteDir = yield* createBareRemote();
1699 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);1699 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1700 fs.writeFileSync(path.join(repoDir, "push-only.txt"), "push only\n");1700 NodeFS.writeFileSync(NodePath.join(repoDir, "push-only.txt"), "push only\n");
1701 yield* runGit(repoDir, ["add", "push-only.txt"]);1701 yield* runGit(repoDir, ["add", "push-only.txt"]);
1702 yield* runGit(repoDir, ["commit", "-m", "Push only branch"]);1702 yield* runGit(repoDir, ["commit", "-m", "Push only branch"]);
17031703
...@@ -1725,11 +1725,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1725,11 +1725,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1725 yield* runGit(repoDir, ["checkout", "-b", "feature/push-dirty"]);1725 yield* runGit(repoDir, ["checkout", "-b", "feature/push-dirty"]);
1726 const remoteDir = yield* createBareRemote();1726 const remoteDir = yield* createBareRemote();
1727 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);1727 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1728 fs.writeFileSync(path.join(repoDir, "push-dirty.txt"), "push dirty\n");1728 NodeFS.writeFileSync(NodePath.join(repoDir, "push-dirty.txt"), "push dirty\n");
1729 yield* runGit(repoDir, ["add", "push-dirty.txt"]);1729 yield* runGit(repoDir, ["add", "push-dirty.txt"]);
1730 yield* runGit(repoDir, ["commit", "-m", "Push dirty branch"]);1730 yield* runGit(repoDir, ["commit", "-m", "Push dirty branch"]);
1731 fs.mkdirSync(path.join(repoDir, ".vercel"));1731 NodeFS.mkdirSync(NodePath.join(repoDir, ".vercel"));
1732 fs.writeFileSync(path.join(repoDir, ".vercel", "project.json"), "{}\n");1732 NodeFS.writeFileSync(NodePath.join(repoDir, ".vercel", "project.json"), "{}\n");
17331733
1734 const { manager } = yield* makeManager();1734 const { manager } = yield* makeManager();
1735 const result = yield* runStackedAction(manager, {1735 const result = yield* runStackedAction(manager, {
...@@ -1760,7 +1760,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1760,7 +1760,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1760 yield* runGit(repoDir, ["checkout", "-b", "feature/create-pr-only"]);1760 yield* runGit(repoDir, ["checkout", "-b", "feature/create-pr-only"]);
1761 const remoteDir = yield* createBareRemote();1761 const remoteDir = yield* createBareRemote();
1762 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);1762 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1763 fs.writeFileSync(path.join(repoDir, "create-pr-only.txt"), "create pr\n");1763 NodeFS.writeFileSync(NodePath.join(repoDir, "create-pr-only.txt"), "create pr\n");
1764 yield* runGit(repoDir, ["add", "create-pr-only.txt"]);1764 yield* runGit(repoDir, ["add", "create-pr-only.txt"]);
1765 yield* runGit(repoDir, ["commit", "-m", "Create PR only branch"]);1765 yield* runGit(repoDir, ["commit", "-m", "Create PR only branch"]);
17661766
...@@ -1805,7 +1805,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1805,7 +1805,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1805 const repoDir = yield* makeTempDir("t3code-git-manager-");1805 const repoDir = yield* makeTempDir("t3code-git-manager-");
1806 yield* initRepo(repoDir);1806 yield* initRepo(repoDir);
1807 yield* runGit(repoDir, ["checkout", "-b", "feature/provider-fallback"]);1807 yield* runGit(repoDir, ["checkout", "-b", "feature/provider-fallback"]);
1808 fs.writeFileSync(path.join(repoDir, "provider-fallback.txt"), "fallback\n");1808 NodeFS.writeFileSync(NodePath.join(repoDir, "provider-fallback.txt"), "fallback\n");
1809 yield* runGit(repoDir, ["add", "provider-fallback.txt"]);1809 yield* runGit(repoDir, ["add", "provider-fallback.txt"]);
1810 yield* runGit(repoDir, ["commit", "-m", "Provider fallback"]);1810 yield* runGit(repoDir, ["commit", "-m", "Provider fallback"]);
1811 const remoteDir = yield* createBareRemote();1811 const remoteDir = yield* createBareRemote();
...@@ -1982,7 +1982,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -1982,7 +1982,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
1982 yield* runGit(repoDir, ["checkout", "main"]);1982 yield* runGit(repoDir, ["checkout", "main"]);
1983 yield* runGit(repoDir, ["branch", "-D", "effect-atom"]);1983 yield* runGit(repoDir, ["branch", "-D", "effect-atom"]);
1984 yield* runGit(repoDir, ["checkout", "--track", "my-org/upstream/effect-atom"]);1984 yield* runGit(repoDir, ["checkout", "--track", "my-org/upstream/effect-atom"]);
1985 fs.writeFileSync(path.join(repoDir, "changes.txt"), "change\n");1985 NodeFS.writeFileSync(NodePath.join(repoDir, "changes.txt"), "change\n");
1986 yield* runGit(repoDir, ["add", "changes.txt"]);1986 yield* runGit(repoDir, ["add", "changes.txt"]);
1987 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);1987 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);
19881988
...@@ -2200,7 +2200,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2200,7 +2200,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2200 yield* runGit(repoDir, ["checkout", "-b", "feature-create-pr"]);2200 yield* runGit(repoDir, ["checkout", "-b", "feature-create-pr"]);
2201 const remoteDir = yield* createBareRemote();2201 const remoteDir = yield* createBareRemote();
2202 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);2202 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
2203 fs.writeFileSync(path.join(repoDir, "changes.txt"), "change\n");2203 NodeFS.writeFileSync(NodePath.join(repoDir, "changes.txt"), "change\n");
2204 yield* runGit(repoDir, ["add", "changes.txt"]);2204 yield* runGit(repoDir, ["add", "changes.txt"]);
2205 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);2205 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);
2206 yield* runGit(repoDir, ["push", "-u", "origin", "feature-create-pr"]);2206 yield* runGit(repoDir, ["push", "-u", "origin", "feature-create-pr"]);
...@@ -2252,7 +2252,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2252,7 +2252,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2252 yield* runGit(peerDir, ["clone", remoteDir, "."]);2252 yield* runGit(peerDir, ["clone", remoteDir, "."]);
2253 yield* runGit(peerDir, ["config", "user.email", "peer@example.com"]);2253 yield* runGit(peerDir, ["config", "user.email", "peer@example.com"]);
2254 yield* runGit(peerDir, ["config", "user.name", "Peer User"]);2254 yield* runGit(peerDir, ["config", "user.name", "Peer User"]);
2255 fs.writeFileSync(path.join(peerDir, "remote.txt"), "remote\n");2255 NodeFS.writeFileSync(NodePath.join(peerDir, "remote.txt"), "remote\n");
2256 yield* runGit(peerDir, ["add", "remote.txt"]);2256 yield* runGit(peerDir, ["add", "remote.txt"]);
2257 yield* runGit(peerDir, ["commit", "-m", "Remote base commit"]);2257 yield* runGit(peerDir, ["commit", "-m", "Remote base commit"]);
2258 yield* runGit(peerDir, ["push", "origin", "main"]);2258 yield* runGit(peerDir, ["push", "origin", "main"]);
...@@ -2265,7 +2265,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2265,7 +2265,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2265 "feature/remote-base",2265 "feature/remote-base",
2266 "origin/main",2266 "origin/main",
2267 ]);2267 ]);
2268 fs.writeFileSync(path.join(repoDir, "feature.txt"), "feature\n");2268 NodeFS.writeFileSync(NodePath.join(repoDir, "feature.txt"), "feature\n");
2269 yield* runGit(repoDir, ["add", "feature.txt"]);2269 yield* runGit(repoDir, ["add", "feature.txt"]);
2270 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);2270 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);
2271 yield* runGit(repoDir, ["push", "-u", "origin", "feature/remote-base"]);2271 yield* runGit(repoDir, ["push", "-u", "origin", "feature/remote-base"]);
...@@ -2304,7 +2304,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2304,7 +2304,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2304 yield* runGit(repoDir, ["checkout", "-b", "feature/no-fork-match"]);2304 yield* runGit(repoDir, ["checkout", "-b", "feature/no-fork-match"]);
2305 const remoteDir = yield* createBareRemote();2305 const remoteDir = yield* createBareRemote();
2306 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);2306 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
2307 fs.writeFileSync(path.join(repoDir, "changes.txt"), "change\n");2307 NodeFS.writeFileSync(NodePath.join(repoDir, "changes.txt"), "change\n");
2308 yield* runGit(repoDir, ["add", "changes.txt"]);2308 yield* runGit(repoDir, ["add", "changes.txt"]);
2309 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);2309 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);
2310 yield* runGit(repoDir, ["push", "-u", "origin", "feature/no-fork-match"]);2310 yield* runGit(repoDir, ["push", "-u", "origin", "feature/no-fork-match"]);
...@@ -2376,7 +2376,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2376,7 +2376,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2376 const forkDir = yield* createBareRemote();2376 const forkDir = yield* createBareRemote();
2377 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);2377 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
2378 yield* runGit(repoDir, ["checkout", "-b", "statemachine"]);2378 yield* runGit(repoDir, ["checkout", "-b", "statemachine"]);
2379 fs.writeFileSync(path.join(repoDir, "changes.txt"), "change\n");2379 NodeFS.writeFileSync(NodePath.join(repoDir, "changes.txt"), "change\n");
2380 yield* runGit(repoDir, ["add", "changes.txt"]);2380 yield* runGit(repoDir, ["add", "changes.txt"]);
2381 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);2381 yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);
2382 yield* runGit(repoDir, ["push", "-u", "fork-seed", "statemachine"]);2382 yield* runGit(repoDir, ["push", "-u", "fork-seed", "statemachine"]);
...@@ -2556,7 +2556,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2556,7 +2556,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2556 const repoDir = yield* makeTempDir("t3code-git-manager-");2556 const repoDir = yield* makeTempDir("t3code-git-manager-");
2557 yield* initRepo(repoDir);2557 yield* initRepo(repoDir);
2558 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local"]);2558 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local"]);
2559 fs.writeFileSync(path.join(repoDir, "local.txt"), "local\n");2559 NodeFS.writeFileSync(NodePath.join(repoDir, "local.txt"), "local\n");
2560 yield* runGit(repoDir, ["add", "local.txt"]);2560 yield* runGit(repoDir, ["add", "local.txt"]);
2561 yield* runGit(repoDir, ["commit", "-m", "Local PR branch"]);2561 yield* runGit(repoDir, ["commit", "-m", "Local PR branch"]);
25622562
...@@ -2597,7 +2597,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2597,7 +2597,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2597 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);2597 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
2598 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);2598 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
2599 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local-upstream"]);2599 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local-upstream"]);
2600 fs.writeFileSync(path.join(repoDir, "upstream.txt"), "upstream\n");2600 NodeFS.writeFileSync(NodePath.join(repoDir, "upstream.txt"), "upstream\n");
2601 yield* runGit(repoDir, ["add", "upstream.txt"]);2601 yield* runGit(repoDir, ["add", "upstream.txt"]);
2602 yield* runGit(repoDir, ["commit", "-m", "Local upstream PR branch"]);2602 yield* runGit(repoDir, ["commit", "-m", "Local upstream PR branch"]);
2603 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-local-upstream"]);2603 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-local-upstream"]);
...@@ -2655,7 +2655,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2655,7 +2655,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2655 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);2655 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
2656 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);2656 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
2657 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local-no-head-repo"]);2657 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local-no-head-repo"]);
2658 fs.writeFileSync(path.join(repoDir, "no-head-repo.txt"), "upstream\n");2658 NodeFS.writeFileSync(NodePath.join(repoDir, "no-head-repo.txt"), "upstream\n");
2659 yield* runGit(repoDir, ["add", "no-head-repo.txt"]);2659 yield* runGit(repoDir, ["add", "no-head-repo.txt"]);
2660 yield* runGit(repoDir, ["commit", "-m", "Local PR branch without repo metadata"]);2660 yield* runGit(repoDir, ["commit", "-m", "Local PR branch without repo metadata"]);
2661 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-local-no-head-repo"]);2661 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-local-no-head-repo"]);
...@@ -2702,7 +2702,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2702,7 +2702,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2702 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);2702 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
2703 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);2703 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
2704 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-worktree"]);2704 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-worktree"]);
2705 fs.writeFileSync(path.join(repoDir, "worktree.txt"), "worktree\n");2705 NodeFS.writeFileSync(NodePath.join(repoDir, "worktree.txt"), "worktree\n");
2706 yield* runGit(repoDir, ["add", "worktree.txt"]);2706 yield* runGit(repoDir, ["add", "worktree.txt"]);
2707 yield* runGit(repoDir, ["commit", "-m", "PR worktree branch"]);2707 yield* runGit(repoDir, ["commit", "-m", "PR worktree branch"]);
2708 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-worktree"]);2708 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-worktree"]);
...@@ -2730,7 +2730,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2730,7 +2730,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
27302730
2731 expect(result.branch).toBe("feature/pr-worktree");2731 expect(result.branch).toBe("feature/pr-worktree");
2732 expect(result.worktreePath).not.toBeNull();2732 expect(result.worktreePath).not.toBeNull();
2733 expect(fs.existsSync(result.worktreePath as string)).toBe(true);2733 expect(NodeFS.existsSync(result.worktreePath as string)).toBe(true);
2734 const worktreeBranch = (yield* runGit(result.worktreePath as string, [2734 const worktreeBranch = (yield* runGit(result.worktreePath as string, [
2735 "branch",2735 "branch",
2736 "--show-current",2736 "--show-current",
...@@ -2747,7 +2747,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2747,7 +2747,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2747 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);2747 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
2748 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);2748 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
2749 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-worktree-setup"]);2749 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-worktree-setup"]);
2750 fs.writeFileSync(path.join(repoDir, "setup.txt"), "setup\n");2750 NodeFS.writeFileSync(NodePath.join(repoDir, "setup.txt"), "setup\n");
2751 yield* runGit(repoDir, ["add", "setup.txt"]);2751 yield* runGit(repoDir, ["add", "setup.txt"]);
2752 yield* runGit(repoDir, ["commit", "-m", "PR worktree setup branch"]);2752 yield* runGit(repoDir, ["commit", "-m", "PR worktree setup branch"]);
2753 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-worktree-setup"]);2753 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-worktree-setup"]);
...@@ -2802,7 +2802,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2802,7 +2802,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2802 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);2802 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
2803 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);2803 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
2804 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-fork"]);2804 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-fork"]);
2805 fs.writeFileSync(path.join(repoDir, "fork.txt"), "fork\n");2805 NodeFS.writeFileSync(NodePath.join(repoDir, "fork.txt"), "fork\n");
2806 yield* runGit(repoDir, ["add", "fork.txt"]);2806 yield* runGit(repoDir, ["add", "fork.txt"]);
2807 yield* runGit(repoDir, ["commit", "-m", "Fork PR branch"]);2807 yield* runGit(repoDir, ["commit", "-m", "Fork PR branch"]);
2808 yield* runGit(repoDir, ["push", "-u", "fork-seed", "feature/pr-fork"]);2808 yield* runGit(repoDir, ["push", "-u", "fork-seed", "feature/pr-fork"]);
...@@ -2864,7 +2864,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2864,7 +2864,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2864 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);2864 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
2865 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);2865 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
2866 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local-fork"]);2866 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local-fork"]);
2867 fs.writeFileSync(path.join(repoDir, "local-fork.txt"), "local fork\n");2867 NodeFS.writeFileSync(NodePath.join(repoDir, "local-fork.txt"), "local fork\n");
2868 yield* runGit(repoDir, ["add", "local-fork.txt"]);2868 yield* runGit(repoDir, ["add", "local-fork.txt"]);
2869 yield* runGit(repoDir, ["commit", "-m", "Local fork PR branch"]);2869 yield* runGit(repoDir, ["commit", "-m", "Local fork PR branch"]);
2870 yield* runGit(repoDir, ["push", "-u", "fork-seed", "feature/pr-local-fork"]);2870 yield* runGit(repoDir, ["push", "-u", "fork-seed", "feature/pr-local-fork"]);
...@@ -2917,7 +2917,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2917,7 +2917,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2917 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);2917 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
2918 yield* runGit(repoDir, ["remote", "add", "binbandit-seed", forkDir]);2918 yield* runGit(repoDir, ["remote", "add", "binbandit-seed", forkDir]);
2919 yield* runGit(repoDir, ["checkout", "-b", "fix/git-action-default-without-origin"]);2919 yield* runGit(repoDir, ["checkout", "-b", "fix/git-action-default-without-origin"]);
2920 fs.writeFileSync(path.join(repoDir, "derived-fork.txt"), "derived fork\n");2920 NodeFS.writeFileSync(NodePath.join(repoDir, "derived-fork.txt"), "derived fork\n");
2921 yield* runGit(repoDir, ["add", "derived-fork.txt"]);2921 yield* runGit(repoDir, ["add", "derived-fork.txt"]);
2922 yield* runGit(repoDir, ["commit", "-m", "Derived fork PR branch"]);2922 yield* runGit(repoDir, ["commit", "-m", "Derived fork PR branch"]);
2923 yield* runGit(repoDir, [2923 yield* runGit(repoDir, [
...@@ -2969,11 +2969,15 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -2969,11 +2969,15 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
2969 const repoDir = yield* makeTempDir("t3code-git-manager-");2969 const repoDir = yield* makeTempDir("t3code-git-manager-");
2970 yield* initRepo(repoDir);2970 yield* initRepo(repoDir);
2971 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-existing-worktree"]);2971 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-existing-worktree"]);
2972 fs.writeFileSync(path.join(repoDir, "existing.txt"), "existing\n");2972 NodeFS.writeFileSync(NodePath.join(repoDir, "existing.txt"), "existing\n");
2973 yield* runGit(repoDir, ["add", "existing.txt"]);2973 yield* runGit(repoDir, ["add", "existing.txt"]);
2974 yield* runGit(repoDir, ["commit", "-m", "Existing worktree branch"]);2974 yield* runGit(repoDir, ["commit", "-m", "Existing worktree branch"]);
2975 yield* runGit(repoDir, ["checkout", "main"]);2975 yield* runGit(repoDir, ["checkout", "main"]);
2976 const worktreePath = path.join(repoDir, "..", `pr-existing-${path.basename(repoDir)}`);2976 const worktreePath = NodePath.join(
2977 repoDir,
2978 "..",
2979 `pr-existing-${NodePath.basename(repoDir)}`,
2980 );
2977 yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-existing-worktree"]);2981 yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-existing-worktree"]);
29782982
2979 const setupCalls: ProjectSetupScriptRunner.ProjectSetupScriptRunnerInput[] = [];2983 const setupCalls: ProjectSetupScriptRunner.ProjectSetupScriptRunnerInput[] = [];
...@@ -3004,8 +3008,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3004,8 +3008,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3004 threadId: asThreadId("thread-pr-existing-worktree"),3008 threadId: asThreadId("thread-pr-existing-worktree"),
3005 });3009 });
30063010
3007 expect(result.worktreePath && fs.realpathSync.native(result.worktreePath)).toBe(3011 expect(result.worktreePath && NodeFS.realpathSync.native(result.worktreePath)).toBe(
3008 fs.realpathSync.native(worktreePath),3012 NodeFS.realpathSync.native(worktreePath),
3009 );3013 );
3010 expect(result.branch).toBe("feature/pr-existing-worktree");3014 expect(result.branch).toBe("feature/pr-existing-worktree");
3011 expect(setupCalls).toHaveLength(0);3015 expect(setupCalls).toHaveLength(0);
...@@ -3024,7 +3028,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3024,7 +3028,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3024 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);3028 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
3025 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);3029 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
3026 yield* runGit(repoDir, ["checkout", "-b", "fork-main-source"]);3030 yield* runGit(repoDir, ["checkout", "-b", "fork-main-source"]);
3027 fs.writeFileSync(path.join(repoDir, "fork-main.txt"), "fork main\n");3031 NodeFS.writeFileSync(NodePath.join(repoDir, "fork-main.txt"), "fork main\n");
3028 yield* runGit(repoDir, ["add", "fork-main.txt"]);3032 yield* runGit(repoDir, ["add", "fork-main.txt"]);
3029 yield* runGit(repoDir, ["commit", "-m", "Fork main branch"]);3033 yield* runGit(repoDir, ["commit", "-m", "Fork main branch"]);
3030 yield* runGit(repoDir, ["push", "-u", "fork-seed", "fork-main-source:main"]);3034 yield* runGit(repoDir, ["push", "-u", "fork-seed", "fork-main-source:main"]);
...@@ -3084,7 +3088,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3084,7 +3088,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3084 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);3088 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
3085 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);3089 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
3086 yield* runGit(repoDir, ["checkout", "-b", "fork-main-source"]);3090 yield* runGit(repoDir, ["checkout", "-b", "fork-main-source"]);
3087 fs.writeFileSync(path.join(repoDir, "fork-main-second.txt"), "fork main second\n");3091 NodeFS.writeFileSync(NodePath.join(repoDir, "fork-main-second.txt"), "fork main second\n");
3088 yield* runGit(repoDir, ["add", "fork-main-second.txt"]);3092 yield* runGit(repoDir, ["add", "fork-main-second.txt"]);
3089 yield* runGit(repoDir, ["commit", "-m", "Fork main second branch"]);3093 yield* runGit(repoDir, ["commit", "-m", "Fork main second branch"]);
3090 yield* runGit(repoDir, ["push", "-u", "fork-seed", "fork-main-source:main"]);3094 yield* runGit(repoDir, ["push", "-u", "fork-seed", "fork-main-source:main"]);
...@@ -3142,12 +3146,16 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3142,12 +3146,16 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3142 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);3146 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
3143 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);3147 yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
3144 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-reused-fork"]);3148 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-reused-fork"]);
3145 fs.writeFileSync(path.join(repoDir, "reused-fork.txt"), "reused fork\n");3149 NodeFS.writeFileSync(NodePath.join(repoDir, "reused-fork.txt"), "reused fork\n");
3146 yield* runGit(repoDir, ["add", "reused-fork.txt"]);3150 yield* runGit(repoDir, ["add", "reused-fork.txt"]);
3147 yield* runGit(repoDir, ["commit", "-m", "Reused fork PR branch"]);3151 yield* runGit(repoDir, ["commit", "-m", "Reused fork PR branch"]);
3148 yield* runGit(repoDir, ["push", "-u", "fork-seed", "feature/pr-reused-fork"]);3152 yield* runGit(repoDir, ["push", "-u", "fork-seed", "feature/pr-reused-fork"]);
3149 yield* runGit(repoDir, ["checkout", "main"]);3153 yield* runGit(repoDir, ["checkout", "main"]);
3150 const worktreePath = path.join(repoDir, "..", `pr-reused-fork-${path.basename(repoDir)}`);3154 const worktreePath = NodePath.join(
3155 repoDir,
3156 "..",
3157 `pr-reused-fork-${NodePath.basename(repoDir)}`,
3158 );
3151 yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-reused-fork"]);3159 yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-reused-fork"]);
3152 yield* runGit(worktreePath, ["branch", "--unset-upstream"], true);3160 yield* runGit(worktreePath, ["branch", "--unset-upstream"], true);
31533161
...@@ -3179,8 +3187,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3179,8 +3187,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3179 mode: "worktree",3187 mode: "worktree",
3180 });3188 });
31813189
3182 expect(result.worktreePath && fs.realpathSync.native(result.worktreePath)).toBe(3190 expect(result.worktreePath && NodeFS.realpathSync.native(result.worktreePath)).toBe(
3183 fs.realpathSync.native(worktreePath),3191 NodeFS.realpathSync.native(worktreePath),
3184 );3192 );
3185 expect(3193 expect(
3186 (yield* runGit(worktreePath, ["rev-parse", "--abbrev-ref", "@{upstream}"])).stdout.trim(),3194 (yield* runGit(worktreePath, ["rev-parse", "--abbrev-ref", "@{upstream}"])).stdout.trim(),
...@@ -3196,7 +3204,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3196,7 +3204,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3196 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);3204 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
3197 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);3205 yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
3198 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-setup-failure"]);3206 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-setup-failure"]);
3199 fs.writeFileSync(path.join(repoDir, "setup-failure.txt"), "setup failure\n");3207 NodeFS.writeFileSync(NodePath.join(repoDir, "setup-failure.txt"), "setup failure\n");
3200 yield* runGit(repoDir, ["add", "setup-failure.txt"]);3208 yield* runGit(repoDir, ["add", "setup-failure.txt"]);
3201 yield* runGit(repoDir, ["commit", "-m", "PR setup failure branch"]);3209 yield* runGit(repoDir, ["commit", "-m", "PR setup failure branch"]);
3202 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-setup-failure"]);3210 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-setup-failure"]);
...@@ -3236,7 +3244,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3236,7 +3244,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
32363244
3237 expect(result.branch).toBe("feature/pr-setup-failure");3245 expect(result.branch).toBe("feature/pr-setup-failure");
3238 expect(result.worktreePath).not.toBeNull();3246 expect(result.worktreePath).not.toBeNull();
3239 expect(fs.existsSync(result.worktreePath as string)).toBe(true);3247 expect(NodeFS.existsSync(result.worktreePath as string)).toBe(true);
3240 }),3248 }),
3241 );3249 );
32423250
...@@ -3276,9 +3284,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3276,9 +3284,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3276 Effect.gen(function* () {3284 Effect.gen(function* () {
3277 const repoDir = yield* makeTempDir("t3code-git-manager-");3285 const repoDir = yield* makeTempDir("t3code-git-manager-");
3278 yield* initRepo(repoDir);3286 yield* initRepo(repoDir);
3279 fs.writeFileSync(path.join(repoDir, "hooked.txt"), "hooked\n");3287 NodeFS.writeFileSync(NodePath.join(repoDir, "hooked.txt"), "hooked\n");
3280 fs.writeFileSync(3288 NodeFS.writeFileSync(
3281 path.join(repoDir, ".git", "hooks", "pre-commit"),3289 NodePath.join(repoDir, ".git", "hooks", "pre-commit"),
3282 '#!/bin/sh\necho "hook: start" >&2\nsleep 0.05\necho "hook: end" >&2\n',3290 '#!/bin/sh\necho "hook: start" >&2\nsleep 0.05\necho "hook: end" >&2\n',
3283 { mode: 0o755 },3291 { mode: 0o755 },
3284 );3292 );
...@@ -3339,9 +3347,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3339,9 +3347,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3339 Effect.gen(function* () {3347 Effect.gen(function* () {
3340 const repoDir = yield* makeTempDir("t3code-git-manager-");3348 const repoDir = yield* makeTempDir("t3code-git-manager-");
3341 yield* initRepo(repoDir);3349 yield* initRepo(repoDir);
3342 fs.writeFileSync(path.join(repoDir, "hook-failure.txt"), "broken\n");3350 NodeFS.writeFileSync(NodePath.join(repoDir, "hook-failure.txt"), "broken\n");
3343 fs.writeFileSync(3351 NodeFS.writeFileSync(
3344 path.join(repoDir, ".git", "hooks", "pre-commit"),3352 NodePath.join(repoDir, ".git", "hooks", "pre-commit"),
3345 '#!/bin/sh\necho "hook: fail" >&2\nexit 1\n',3353 '#!/bin/sh\necho "hook: fail" >&2\nexit 1\n',
3346 { mode: 0o755 },3354 { mode: 0o755 },
3347 );3355 );
...@@ -3392,7 +3400,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {...@@ -3392,7 +3400,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
3392 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-only-follow-up"]);3400 yield* runGit(repoDir, ["checkout", "-b", "feature/pr-only-follow-up"]);
3393 const remoteDir = yield* createBareRemote();3401 const remoteDir = yield* createBareRemote();
3394 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);3402 yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
3395 fs.writeFileSync(path.join(repoDir, "pr-only.txt"), "pr only\n");3403 NodeFS.writeFileSync(NodePath.join(repoDir, "pr-only.txt"), "pr only\n");
3396 yield* runGit(repoDir, ["add", "pr-only.txt"]);3404 yield* runGit(repoDir, ["add", "pr-only.txt"]);
3397 yield* runGit(repoDir, ["commit", "-m", "PR only branch"]);3405 yield* runGit(repoDir, ["commit", "-m", "PR only branch"]);
3398 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-only-follow-up"]);3406 yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-only-follow-up"]);
apps/server/src/git/Utils.ts+3-3
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { existsSync } from "node:fs";2import * as NodeFS from "node:fs";
3import { join } from "node:path";3import * as NodePath from "node:path";
44
5export function isGitRepository(cwd: string): boolean {5export function isGitRepository(cwd: string): boolean {
6 return existsSync(join(cwd, ".git"));6 return NodeFS.existsSync(NodePath.join(cwd, ".git"));
7}7}
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+17-17
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import os from "node:os";3import * as NodeOS from "node:os";
4import path from "node:path";4import * as NodePath from "node:path";
5import { execFileSync } from "node:child_process";5import * as NodeChildProcess from "node:child_process";
66
7import {7import {
8 ProviderDriverKind,8 ProviderDriverKind,
...@@ -198,7 +198,7 @@ async function waitForEvent(...@@ -198,7 +198,7 @@ async function waitForEvent(
198}198}
199199
200function runGit(cwd: string, args: ReadonlyArray<string>) {200function runGit(cwd: string, args: ReadonlyArray<string>) {
201 return execFileSync("git", args, {201 return NodeChildProcess.execFileSync("git", args, {
202 cwd,202 cwd,
203 stdio: ["ignore", "pipe", "pipe"],203 stdio: ["ignore", "pipe", "pipe"],
204 encoding: "utf8",204 encoding: "utf8",
...@@ -206,11 +206,11 @@ function runGit(cwd: string, args: ReadonlyArray<string>) {...@@ -206,11 +206,11 @@ function runGit(cwd: string, args: ReadonlyArray<string>) {
206}206}
207207
208function createGitRepository() {208function createGitRepository() {
209 const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "t3-checkpoint-handler-"));209 const cwd = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-checkpoint-handler-"));
210 runGit(cwd, ["init", "--initial-branch=main"]);210 runGit(cwd, ["init", "--initial-branch=main"]);
211 runGit(cwd, ["config", "user.email", "test@example.com"]);211 runGit(cwd, ["config", "user.email", "test@example.com"]);
212 runGit(cwd, ["config", "user.name", "Test User"]);212 runGit(cwd, ["config", "user.name", "Test User"]);
213 fs.writeFileSync(path.join(cwd, "README.md"), "v1\n", "utf8");213 NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), "v1\n", "utf8");
214 runGit(cwd, ["add", "."]);214 runGit(cwd, ["add", "."]);
215 runGit(cwd, ["commit", "-m", "Initial"]);215 runGit(cwd, ["commit", "-m", "Initial"]);
216 return cwd;216 return cwd;
...@@ -267,7 +267,7 @@ describe("CheckpointReactor", () => {...@@ -267,7 +267,7 @@ describe("CheckpointReactor", () => {
267 while (tempDirs.length > 0) {267 while (tempDirs.length > 0) {
268 const dir = tempDirs.pop();268 const dir = tempDirs.pop();
269 if (dir) {269 if (dir) {
270 fs.rmSync(dir, { recursive: true, force: true });270 NodeFS.rmSync(dir, { recursive: true, force: true });
271 }271 }
272 }272 }
273 });273 });
...@@ -395,14 +395,14 @@ describe("CheckpointReactor", () => {...@@ -395,14 +395,14 @@ describe("CheckpointReactor", () => {
395 checkpointRef: checkpointRefForThreadTurn(ThreadId.make("thread-1"), 0),395 checkpointRef: checkpointRefForThreadTurn(ThreadId.make("thread-1"), 0),
396 }),396 }),
397 );397 );
398 fs.writeFileSync(path.join(cwd, "README.md"), "v2\n", "utf8");398 NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), "v2\n", "utf8");
399 await runtime.runPromise(399 await runtime.runPromise(
400 checkpointStore.captureCheckpoint({400 checkpointStore.captureCheckpoint({
401 cwd,401 cwd,
402 checkpointRef: checkpointRefForThreadTurn(ThreadId.make("thread-1"), 1),402 checkpointRef: checkpointRefForThreadTurn(ThreadId.make("thread-1"), 1),
403 }),403 }),
404 );404 );
405 fs.writeFileSync(path.join(cwd, "README.md"), "v3\n", "utf8");405 NodeFS.writeFileSync(NodePath.join(cwd, "README.md"), "v3\n", "utf8");
406 await runtime.runPromise(406 await runtime.runPromise(
407 checkpointStore.captureCheckpoint({407 checkpointStore.captureCheckpoint({
408 cwd,408 cwd,
...@@ -456,7 +456,7 @@ describe("CheckpointReactor", () => {...@@ -456,7 +456,7 @@ describe("CheckpointReactor", () => {
456 checkpointRefForThreadTurn(ThreadId.make("thread-1"), 0),456 checkpointRefForThreadTurn(ThreadId.make("thread-1"), 0),
457 );457 );
458458
459 fs.writeFileSync(path.join(harness.cwd, "README.md"), "v2\n", "utf8");459 NodeFS.writeFileSync(NodePath.join(harness.cwd, "README.md"), "v2\n", "utf8");
460 harness.provider.emit({460 harness.provider.emit({
461 type: "turn.completed",461 type: "turn.completed",
462 eventId: EventId.make("evt-turn-completed-1"),462 eventId: EventId.make("evt-turn-completed-1"),
...@@ -554,7 +554,7 @@ describe("CheckpointReactor", () => {...@@ -554,7 +554,7 @@ describe("CheckpointReactor", () => {
554 checkpointRefForThreadTurn(ThreadId.make("thread-1"), 0),554 checkpointRefForThreadTurn(ThreadId.make("thread-1"), 0),
555 );555 );
556556
557 fs.writeFileSync(path.join(harness.cwd, "README.md"), "v2\n", "utf8");557 NodeFS.writeFileSync(NodePath.join(harness.cwd, "README.md"), "v2\n", "utf8");
558558
559 harness.provider.emit({559 harness.provider.emit({
560 type: "turn.completed",560 type: "turn.completed",
...@@ -628,7 +628,7 @@ describe("CheckpointReactor", () => {...@@ -628,7 +628,7 @@ describe("CheckpointReactor", () => {
628 checkpointRefForThreadTurn(ThreadId.make("thread-1"), 0),628 checkpointRefForThreadTurn(ThreadId.make("thread-1"), 0),
629 );629 );
630630
631 fs.writeFileSync(path.join(harness.cwd, "README.md"), "v2\n", "utf8");631 NodeFS.writeFileSync(NodePath.join(harness.cwd, "README.md"), "v2\n", "utf8");
632 harness.provider.emit({632 harness.provider.emit({
633 type: "turn.completed",633 type: "turn.completed",
634 eventId: EventId.make("evt-turn-completed-claude-1"),634 eventId: EventId.make("evt-turn-completed-claude-1"),
...@@ -761,7 +761,7 @@ describe("CheckpointReactor", () => {...@@ -761,7 +761,7 @@ describe("CheckpointReactor", () => {
761 }),761 }),
762 );762 );
763763
764 fs.writeFileSync(path.join(harness.cwd, "README.md"), "v2\n", "utf8");764 NodeFS.writeFileSync(NodePath.join(harness.cwd, "README.md"), "v2\n", "utf8");
765 harness.provider.emit({765 harness.provider.emit({
766 type: "turn.completed",766 type: "turn.completed",
767 eventId: EventId.make("evt-turn-completed-missing-provider-cwd"),767 eventId: EventId.make("evt-turn-completed-missing-provider-cwd"),
...@@ -829,8 +829,8 @@ describe("CheckpointReactor", () => {...@@ -829,8 +829,8 @@ describe("CheckpointReactor", () => {
829 });829 });
830830
831 it("continues processing runtime events after a single checkpoint runtime failure", async () => {831 it("continues processing runtime events after a single checkpoint runtime failure", async () => {
832 const nonRepositorySessionCwd = fs.mkdtempSync(832 const nonRepositorySessionCwd = NodeFS.mkdtempSync(
833 path.join(os.tmpdir(), "t3-checkpoint-runtime-non-repo-"),833 NodePath.join(NodeOS.tmpdir(), "t3-checkpoint-runtime-non-repo-"),
834 );834 );
835 tempDirs.push(nonRepositorySessionCwd);835 tempDirs.push(nonRepositorySessionCwd);
836836
...@@ -963,7 +963,7 @@ describe("CheckpointReactor", () => {...@@ -963,7 +963,7 @@ describe("CheckpointReactor", () => {
963 threadId: ThreadId.make("thread-1"),963 threadId: ThreadId.make("thread-1"),
964 numTurns: 1,964 numTurns: 1,
965 });965 });
966 expect(fs.readFileSync(path.join(harness.cwd, "README.md"), "utf8")).toBe("v2\n");966 expect(NodeFS.readFileSync(NodePath.join(harness.cwd, "README.md"), "utf8")).toBe("v2\n");
967 expect(967 expect(
968 gitRefExists(harness.cwd, checkpointRefForThreadTurn(ThreadId.make("thread-1"), 2)),968 gitRefExists(harness.cwd, checkpointRefForThreadTurn(ThreadId.make("thread-1"), 2)),
969 ).toBe(false);969 ).toBe(false);
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+7-6
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import os from "node:os";3import * as NodeOS from "node:os";
4import path from "node:path";4import * as NodePath from "node:path";
55
6import {6import {
7 ModelSelection,7 ModelSelection,
...@@ -107,11 +107,11 @@ describe("ProviderCommandReactor", () => {...@@ -107,11 +107,11 @@ describe("ProviderCommandReactor", () => {
107 }107 }
108 runtime = null;108 runtime = null;
109 for (const stateDir of createdStateDirs) {109 for (const stateDir of createdStateDirs) {
110 fs.rmSync(stateDir, { recursive: true, force: true });110 NodeFS.rmSync(stateDir, { recursive: true, force: true });
111 }111 }
112 createdStateDirs.clear();112 createdStateDirs.clear();
113 for (const baseDir of createdBaseDirs) {113 for (const baseDir of createdBaseDirs) {
114 fs.rmSync(baseDir, { recursive: true, force: true });114 NodeFS.rmSync(baseDir, { recursive: true, force: true });
115 }115 }
116 createdBaseDirs.clear();116 createdBaseDirs.clear();
117 });117 });
...@@ -147,7 +147,8 @@ describe("ProviderCommandReactor", () => {...@@ -147,7 +147,8 @@ describe("ProviderCommandReactor", () => {
147 readonly requiresNewThreadForModelChange?: boolean;147 readonly requiresNewThreadForModelChange?: boolean;
148 }) {148 }) {
149 const now = "2026-01-01T00:00:00.000Z";149 const now = "2026-01-01T00:00:00.000Z";
150 const baseDir = input?.baseDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "t3code-reactor-"));150 const baseDir =
151 input?.baseDir ?? NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3code-reactor-"));
151 createdBaseDirs.add(baseDir);152 createdBaseDirs.add(baseDir);
152 const { stateDir } = deriveServerPathsSync(baseDir, undefined);153 const { stateDir } = deriveServerPathsSync(baseDir, undefined);
153 createdStateDirs.add(stateDir);154 createdStateDirs.add(stateDir);
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+6-6
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import os from "node:os";3import * as NodeOS from "node:os";
4import path from "node:path";4import * as NodePath from "node:path";
55
6import {6import {
7 OrchestrationReadModel,7 OrchestrationReadModel,
...@@ -198,7 +198,7 @@ describe("ProviderRuntimeIngestion", () => {...@@ -198,7 +198,7 @@ describe("ProviderRuntimeIngestion", () => {
198 const tempDirs: string[] = [];198 const tempDirs: string[] = [];
199199
200 function makeTempDir(prefix: string): string {200 function makeTempDir(prefix: string): string {
201 const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));201 const dir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), prefix));
202 tempDirs.push(dir);202 tempDirs.push(dir);
203 return dir;203 return dir;
204 }204 }
...@@ -213,13 +213,13 @@ describe("ProviderRuntimeIngestion", () => {...@@ -213,13 +213,13 @@ describe("ProviderRuntimeIngestion", () => {
213 }213 }
214 runtime = null;214 runtime = null;
215 for (const dir of tempDirs.splice(0)) {215 for (const dir of tempDirs.splice(0)) {
216 fs.rmSync(dir, { recursive: true, force: true });216 NodeFS.rmSync(dir, { recursive: true, force: true });
217 }217 }
218 });218 });
219219
220 async function createHarness(options?: { serverSettings?: Partial<ServerSettings> }) {220 async function createHarness(options?: { serverSettings?: Partial<ServerSettings> }) {
221 const workspaceRoot = makeTempDir("t3-provider-project-");221 const workspaceRoot = makeTempDir("t3-provider-project-");
222 fs.mkdirSync(path.join(workspaceRoot, ".git"));222 NodeFS.mkdirSync(NodePath.join(workspaceRoot, ".git"));
223 const provider = createProviderServiceHarness();223 const provider = createProviderServiceHarness();
224 const orchestrationLayer = OrchestrationEngineLive.pipe(224 const orchestrationLayer = OrchestrationEngineLive.pipe(
225 Layer.provide(OrchestrationProjectionSnapshotQueryLive),225 Layer.provide(OrchestrationProjectionSnapshotQueryLive),
apps/server/src/pathExpansion.test.ts+5-5
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { homedir } from "node:os";2import * as NodeOS from "node:os";
3import { join } from "node:path";3import * as NodePath from "node:path";
4import { describe, expect, it } from "vite-plus/test";4import { describe, expect, it } from "vite-plus/test";
55
6import { expandHomePath } from "./pathExpansion.ts";6import { expandHomePath } from "./pathExpansion.ts";
...@@ -17,15 +17,15 @@ describe("expandHomePath", () => {...@@ -17,15 +17,15 @@ describe("expandHomePath", () => {
17 });17 });
1818
19 it("expands a lone tilde to the home directory", () => {19 it("expands a lone tilde to the home directory", () => {
20 expect(expandHomePath("~")).toBe(homedir());20 expect(expandHomePath("~")).toBe(NodeOS.homedir());
21 });21 });
2222
23 it("expands ~/ to a subpath of the home directory", () => {23 it("expands ~/ to a subpath of the home directory", () => {
24 expect(expandHomePath("~/.codex-work")).toBe(join(homedir(), ".codex-work"));24 expect(expandHomePath("~/.codex-work")).toBe(NodePath.join(NodeOS.homedir(), ".codex-work"));
25 });25 });
2626
27 it("expands a Windows-style ~\\ prefix", () => {27 it("expands a Windows-style ~\\ prefix", () => {
28 expect(expandHomePath("~\\.codex")).toBe(join(homedir(), ".codex"));28 expect(expandHomePath("~\\.codex")).toBe(NodePath.join(NodeOS.homedir(), ".codex"));
29 });29 });
3030
31 it("does not expand ~user paths", () => {31 it("does not expand ~user paths", () => {
apps/server/src/pathExpansion.ts+4-4
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { homedir } from "node:os";2import * as NodeOS from "node:os";
3import { join } from "node:path";3import * as NodePath from "node:path";
44
5/**5/**
6 * Expand a leading `~` (or `~/…`, `~\…`) in a user-supplied path to the6 * Expand a leading `~` (or `~/…`, `~\…`) in a user-supplied path to the
...@@ -16,9 +16,9 @@ import { join } from "node:path";...@@ -16,9 +16,9 @@ import { join } from "node:path";
16 */16 */
17export function expandHomePath(value: string): string {17export function expandHomePath(value: string): string {
18 if (!value) return value;18 if (!value) return value;
19 if (value === "~") return homedir();19 if (value === "~") return NodeOS.homedir();
20 if (value.startsWith("~/") || value.startsWith("~\\")) {20 if (value.startsWith("~/") || value.startsWith("~\\")) {
21 return join(homedir(), value.slice(2));21 return NodePath.join(NodeOS.homedir(), value.slice(2));
22 }22 }
23 return value;23 return value;
24}24}
apps/server/src/persistence/NodeSqliteClient.ts+11-7
...@@ -4,7 +4,7 @@...@@ -4,7 +4,7 @@
4 *4 *
5 * @module SqliteClient5 * @module SqliteClient
6 */6 */
7import { DatabaseSync, type StatementSync } from "node:sqlite";7import * as NodeSqlite from "node:sqlite";
88
9import * as Cache from "effect/Cache";9import * as Cache from "effect/Cache";
10import * as Config from "effect/Config";10import * as Config from "effect/Config";
...@@ -69,7 +69,7 @@ const checkNodeSqliteCompat = () => {...@@ -69,7 +69,7 @@ const checkNodeSqliteCompat = () => {
6969
70const makeWithDatabase = Effect.fn("makeWithDatabase")(function* (70const makeWithDatabase = Effect.fn("makeWithDatabase")(function* (
71 options: SqliteClientConfig,71 options: SqliteClientConfig,
72 openDatabase: () => DatabaseSync,72 openDatabase: () => NodeSqlite.DatabaseSync,
73): Effect.fn.Return<Client.SqlClient, never, Scope.Scope | Reactivity.Reactivity> {73): Effect.fn.Return<Client.SqlClient, never, Scope.Scope | Reactivity.Reactivity> {
74 yield* checkNodeSqliteCompat();74 yield* checkNodeSqliteCompat();
7575
...@@ -86,8 +86,8 @@ const makeWithDatabase = Effect.fn("makeWithDatabase")(function* (...@@ -86,8 +86,8 @@ const makeWithDatabase = Effect.fn("makeWithDatabase")(function* (
86 Effect.sync(() => db.close()),86 Effect.sync(() => db.close()),
87 );87 );
8888
89 const statementReaderCache = new WeakMap<StatementSync, boolean>();89 const statementReaderCache = new WeakMap<NodeSqlite.StatementSync, boolean>();
90 const hasRows = (statement: StatementSync): boolean => {90 const hasRows = (statement: NodeSqlite.StatementSync): boolean => {
91 const cached = statementReaderCache.get(statement);91 const cached = statementReaderCache.get(statement);
92 if (cached !== undefined) {92 if (cached !== undefined) {
93 return cached;93 return cached;
...@@ -113,7 +113,11 @@ const makeWithDatabase = Effect.fn("makeWithDatabase")(function* (...@@ -113,7 +113,11 @@ const makeWithDatabase = Effect.fn("makeWithDatabase")(function* (
113 }),113 }),
114 });114 });
115115
116 const runStatement = (statement: StatementSync, params: ReadonlyArray<unknown>, raw: boolean) =>116 const runStatement = (
117 statement: NodeSqlite.StatementSync,
118 params: ReadonlyArray<unknown>,
119 raw: boolean,
120 ) =>
117 Effect.withFiber<ReadonlyArray<any>, SqlError>((fiber) => {121 Effect.withFiber<ReadonlyArray<any>, SqlError>((fiber) => {
118 statement.setReadBigInts(Boolean(Context.get(fiber.context, Client.SafeIntegers)));122 statement.setReadBigInts(Boolean(Context.get(fiber.context, Client.SafeIntegers)));
119 try {123 try {
...@@ -220,7 +224,7 @@ const make = (...@@ -220,7 +224,7 @@ const make = (
220 makeWithDatabase(224 makeWithDatabase(
221 options,225 options,
222 () =>226 () =>
223 new DatabaseSync(options.filename, {227 new NodeSqlite.DatabaseSync(options.filename, {
224 readOnly: options.readonly ?? false,228 readOnly: options.readonly ?? false,
225 allowExtension: options.allowExtension ?? false,229 allowExtension: options.allowExtension ?? false,
226 }),230 }),
...@@ -236,7 +240,7 @@ const makeMemory = (...@@ -236,7 +240,7 @@ const makeMemory = (
236 readonly: false,240 readonly: false,
237 },241 },
238 () => {242 () => {
239 const database = new DatabaseSync(":memory:", {243 const database = new NodeSqlite.DatabaseSync(":memory:", {
240 allowExtension: config.allowExtension ?? false,244 allowExtension: config.allowExtension ?? false,
241 });245 });
242 return database;246 return database;
apps/server/src/preview/PortScanner.test.ts+4-4
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import * as net from "node:net";1import * as NodeNet from "node:net";
22
3import { it as effectIt } from "@effect/vitest";3import { it as effectIt } from "@effect/vitest";
4import { HostProcessPlatform } from "@t3tools/shared/hostProcess";4import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
...@@ -17,9 +17,9 @@ const TestPortDiscoveryLive = PortScanner.layer.pipe(...@@ -17,9 +17,9 @@ const TestPortDiscoveryLive = PortScanner.layer.pipe(
17 ),17 ),
18);18);
1919
20const openServer = (port: number): Effect.Effect<net.Server | null> =>20const openServer = (port: number): Effect.Effect<NodeNet.Server | null> =>
21 Effect.callback((resume) => {21 Effect.callback((resume) => {
22 const server = net.createServer();22 const server = NodeNet.createServer();
23 server.once("error", () => {23 server.once("error", () => {
24 resume(Effect.succeed(null));24 resume(Effect.succeed(null));
25 });25 });
...@@ -31,7 +31,7 @@ const openServer = (port: number): Effect.Effect<net.Server | null> =>...@@ -31,7 +31,7 @@ const openServer = (port: number): Effect.Effect<net.Server | null> =>
31 });31 });
32 });32 });
3333
34const closeServer = (server: net.Server): Effect.Effect<void> =>34const closeServer = (server: NodeNet.Server): Effect.Effect<void> =>
35 Effect.callback((resume) => {35 Effect.callback((resume) => {
36 server.close(() => resume(Effect.void));36 server.close(() => resume(Effect.void));
37 });37 });
apps/server/src/provider/Layers/ClaudeAdapter.test.ts+9-9
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";2import * as NodeFS from "node:fs";
3import os from "node:os";3import * as NodeOS from "node:os";
4import path from "node:path";4import * as NodePath from "node:path";
55
6import * as NodeServices from "@effect/platform-node/NodeServices";6import * as NodeServices from "@effect/platform-node/NodeServices";
7import type {7import type {
...@@ -395,7 +395,7 @@ describe("ClaudeAdapterLive", () => {...@@ -395,7 +395,7 @@ describe("ClaudeAdapterLive", () => {
395 });395 });
396396
397 const createInput = harness.getLastCreateQueryInput();397 const createInput = harness.getLastCreateQueryInput();
398 assert.equal(createInput?.options.env?.HOME, path.join(os.homedir(), ".claude-work"));398 assert.equal(createInput?.options.env?.HOME, NodePath.join(NodeOS.homedir(), ".claude-work"));
399 }).pipe(399 }).pipe(
400 Effect.provideService(Random.Random, makeDeterministicRandomService()),400 Effect.provideService(Random.Random, makeDeterministicRandomService()),
401 Effect.provide(harness.layer),401 Effect.provide(harness.layer),
...@@ -649,7 +649,7 @@ describe("ClaudeAdapterLive", () => {...@@ -649,7 +649,7 @@ describe("ClaudeAdapterLive", () => {
649 });649 });
650650
651 it.effect("embeds image attachments in Claude user messages", () => {651 it.effect("embeds image attachments in Claude user messages", () => {
652 const baseDir = mkdtempSync(path.join(os.tmpdir(), "claude-attachments-"));652 const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "claude-attachments-"));
653 const harness = makeHarness({653 const harness = makeHarness({
654 cwd: "/tmp/project-claude-attachments",654 cwd: "/tmp/project-claude-attachments",
655 baseDir,655 baseDir,
...@@ -657,7 +657,7 @@ describe("ClaudeAdapterLive", () => {...@@ -657,7 +657,7 @@ describe("ClaudeAdapterLive", () => {
657 return Effect.gen(function* () {657 return Effect.gen(function* () {
658 yield* Effect.addFinalizer(() =>658 yield* Effect.addFinalizer(() =>
659 Effect.sync(() =>659 Effect.sync(() =>
660 rmSync(baseDir, {660 NodeFS.rmSync(baseDir, {
661 recursive: true,661 recursive: true,
662 force: true,662 force: true,
663 }),663 }),
...@@ -674,9 +674,9 @@ describe("ClaudeAdapterLive", () => {...@@ -674,9 +674,9 @@ describe("ClaudeAdapterLive", () => {
674 mimeType: "image/png",674 mimeType: "image/png",
675 sizeBytes: 4,675 sizeBytes: 4,
676 };676 };
677 const attachmentPath = path.join(attachmentsDir, attachmentRelativePath(attachment));677 const attachmentPath = NodePath.join(attachmentsDir, attachmentRelativePath(attachment));
678 mkdirSync(path.dirname(attachmentPath), { recursive: true });678 NodeFS.mkdirSync(NodePath.dirname(attachmentPath), { recursive: true });
679 writeFileSync(attachmentPath, Uint8Array.from([1, 2, 3, 4]));679 NodeFS.writeFileSync(attachmentPath, Uint8Array.from([1, 2, 3, 4]));
680680
681 const session = yield* adapter.startSession({681 const session = yield* adapter.startSession({
682 threadId: THREAD_ID,682 threadId: THREAD_ID,
apps/server/src/provider/Layers/CodexAdapter.test.ts+98-96
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import assert from "node:assert/strict";2import * as NodeAssert from "node:assert/strict";
3import fs from "node:fs";3import * as NodeFS from "node:fs";
4import os from "node:os";4import * as NodeOS from "node:os";
5import path from "node:path";5import * as NodePath from "node:path";
6import {6import {
7 ApprovalRequestId,7 ApprovalRequestId,
8 CodexSettings,8 CodexSettings,
...@@ -250,8 +250,8 @@ validationLayer("CodexAdapterLive validation", (it) => {...@@ -250,8 +250,8 @@ validationLayer("CodexAdapterLive validation", (it) => {
250 })250 })
251 .pipe(Effect.result);251 .pipe(Effect.result);
252252
253 assert.equal(result._tag, "Failure");253 NodeAssert.equal(result._tag, "Failure");
254 assert.deepStrictEqual(254 NodeAssert.deepStrictEqual(
255 result.failure,255 result.failure,
256 new ProviderAdapterValidationError({256 new ProviderAdapterValidationError({
257 provider: ProviderDriverKind.make("codex"),257 provider: ProviderDriverKind.make("codex"),
...@@ -259,7 +259,7 @@ validationLayer("CodexAdapterLive validation", (it) => {...@@ -259,7 +259,7 @@ validationLayer("CodexAdapterLive validation", (it) => {
259 issue: "Expected provider 'codex' but received 'claudeAgent'.",259 issue: "Expected provider 'codex' but received 'claudeAgent'.",
260 }),260 }),
261 );261 );
262 assert.equal(validationRuntimeFactory.factory.mock.calls.length, 0);262 NodeAssert.equal(validationRuntimeFactory.factory.mock.calls.length, 0);
263 }),263 }),
264 );264 );
265 it.effect("maps codex model options before starting a session", () =>265 it.effect("maps codex model options before starting a session", () =>
...@@ -276,7 +276,7 @@ validationLayer("CodexAdapterLive validation", (it) => {...@@ -276,7 +276,7 @@ validationLayer("CodexAdapterLive validation", (it) => {
276 runtimeMode: "full-access",276 runtimeMode: "full-access",
277 });277 });
278278
279 assert.deepStrictEqual(validationRuntimeFactory.factory.mock.calls[0]?.[0], {279 NodeAssert.deepStrictEqual(validationRuntimeFactory.factory.mock.calls[0]?.[0], {
280 binaryPath: "codex",280 binaryPath: "codex",
281 cwd: process.cwd(),281 cwd: process.cwd(),
282 model: "gpt-5.3-codex",282 model: "gpt-5.3-codex",
...@@ -319,10 +319,10 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {...@@ -319,10 +319,10 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
319 })319 })
320 .pipe(Effect.result);320 .pipe(Effect.result);
321321
322 assert.equal(result._tag, "Failure");322 NodeAssert.equal(result._tag, "Failure");
323 assert.equal(result.failure._tag, "ProviderAdapterSessionNotFoundError");323 NodeAssert.equal(result.failure._tag, "ProviderAdapterSessionNotFoundError");
324 assert.equal(result.failure.provider, "codex");324 NodeAssert.equal(result.failure.provider, "codex");
325 assert.equal(result.failure.threadId, "sess-missing");325 NodeAssert.equal(result.failure.threadId, "sess-missing");
326 }),326 }),
327 );327 );
328328
...@@ -335,7 +335,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {...@@ -335,7 +335,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
335 runtimeMode: "full-access",335 runtimeMode: "full-access",
336 });336 });
337 const runtime = sessionRuntimeFactory.lastRuntime;337 const runtime = sessionRuntimeFactory.lastRuntime;
338 assert.ok(runtime);338 NodeAssert.ok(runtime);
339 runtime.sendTurnImpl.mockClear();339 runtime.sendTurnImpl.mockClear();
340340
341 yield* Effect.ignore(341 yield* Effect.ignore(
...@@ -350,7 +350,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {...@@ -350,7 +350,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
350 }),350 }),
351 );351 );
352352
353 assert.deepStrictEqual(runtime.sendTurnImpl.mock.calls[0]?.[0], {353 NodeAssert.deepStrictEqual(runtime.sendTurnImpl.mock.calls[0]?.[0], {
354 input: "hello",354 input: "hello",
355 model: "gpt-5.3-codex",355 model: "gpt-5.3-codex",
356 effort: "high",356 effort: "high",
...@@ -386,7 +386,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {...@@ -386,7 +386,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
386 runtimeMode: "full-access",386 runtimeMode: "full-access",
387 });387 });
388 const runtime = customRuntimeFactory.lastRuntime;388 const runtime = customRuntimeFactory.lastRuntime;
389 assert.ok(runtime);389 NodeAssert.ok(runtime);
390 runtime.sendTurnImpl.mockClear();390 runtime.sendTurnImpl.mockClear();
391391
392 yield* Effect.ignore(392 yield* Effect.ignore(
...@@ -405,7 +405,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {...@@ -405,7 +405,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => {
405 }),405 }),
406 );406 );
407407
408 assert.deepStrictEqual(runtime.sendTurnImpl.mock.calls[0]?.[0], {408 NodeAssert.deepStrictEqual(runtime.sendTurnImpl.mock.calls[0]?.[0], {
409 input: "hello",409 input: "hello",
410 model: "gpt-5.3-codex",410 model: "gpt-5.3-codex",
411 effort: "high",411 effort: "high",
...@@ -442,7 +442,7 @@ function startLifecycleRuntime() {...@@ -442,7 +442,7 @@ function startLifecycleRuntime() {
442 runtimeMode: "full-access",442 runtimeMode: "full-access",
443 });443 });
444 const runtime = lifecycleRuntimeFactory.lastRuntime;444 const runtime = lifecycleRuntimeFactory.lastRuntime;
445 assert.ok(runtime);445 NodeAssert.ok(runtime);
446 return { adapter, runtime };446 return { adapter, runtime };
447 });447 });
448}448}
...@@ -477,17 +477,17 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -477,17 +477,17 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
477 yield* runtime.emit(event);477 yield* runtime.emit(event);
478 const firstEvent = yield* Fiber.join(firstEventFiber);478 const firstEvent = yield* Fiber.join(firstEventFiber);
479479
480 assert.equal(firstEvent._tag, "Some");480 NodeAssert.equal(firstEvent._tag, "Some");
481 if (firstEvent._tag !== "Some") {481 if (firstEvent._tag !== "Some") {
482 return;482 return;
483 }483 }
484 assert.equal(firstEvent.value.type, "item.completed");484 NodeAssert.equal(firstEvent.value.type, "item.completed");
485 if (firstEvent.value.type !== "item.completed") {485 if (firstEvent.value.type !== "item.completed") {
486 return;486 return;
487 }487 }
488 assert.equal(firstEvent.value.itemId, "msg_1");488 NodeAssert.equal(firstEvent.value.itemId, "msg_1");
489 assert.equal(firstEvent.value.turnId, "turn-1");489 NodeAssert.equal(firstEvent.value.turnId, "turn-1");
490 assert.equal(firstEvent.value.payload.itemType, "assistant_message");490 NodeAssert.equal(firstEvent.value.payload.itemType, "assistant_message");
491 }),491 }),
492 );492 );
493493
...@@ -524,13 +524,13 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -524,13 +524,13 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
524 });524 });
525 const firstEvent = yield* Fiber.join(firstEventFiber);525 const firstEvent = yield* Fiber.join(firstEventFiber);
526526
527 assert.equal(firstEvent._tag, "Some");527 NodeAssert.equal(firstEvent._tag, "Some");
528 if (firstEvent._tag !== "Some" || firstEvent.value.type !== "item.completed") {528 if (firstEvent._tag !== "Some" || firstEvent.value.type !== "item.completed") {
529 return;529 return;
530 }530 }
531 assert.equal(firstEvent.value.payload.itemType, "mcp_tool_call");531 NodeAssert.equal(firstEvent.value.payload.itemType, "mcp_tool_call");
532 assert.equal(firstEvent.value.payload.title, "t3-code · preview_status");532 NodeAssert.equal(firstEvent.value.payload.title, "t3-code · preview_status");
533 assert.deepStrictEqual(firstEvent.value.payload.data, {533 NodeAssert.deepStrictEqual(firstEvent.value.payload.data, {
534 completedAtMs: 1_778_000_000_000,534 completedAtMs: 1_778_000_000_000,
535 threadId: "thread-1",535 threadId: "thread-1",
536 turnId: "turn-1",536 turnId: "turn-1",
...@@ -578,16 +578,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -578,16 +578,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
578 yield* runtime.emit(event);578 yield* runtime.emit(event);
579 const firstEvent = yield* Fiber.join(firstEventFiber);579 const firstEvent = yield* Fiber.join(firstEventFiber);
580580
581 assert.equal(firstEvent._tag, "Some");581 NodeAssert.equal(firstEvent._tag, "Some");
582 if (firstEvent._tag !== "Some") {582 if (firstEvent._tag !== "Some") {
583 return;583 return;
584 }584 }
585 assert.equal(firstEvent.value.type, "turn.proposed.completed");585 NodeAssert.equal(firstEvent.value.type, "turn.proposed.completed");
586 if (firstEvent.value.type !== "turn.proposed.completed") {586 if (firstEvent.value.type !== "turn.proposed.completed") {
587 return;587 return;
588 }588 }
589 assert.equal(firstEvent.value.turnId, "turn-1");589 NodeAssert.equal(firstEvent.value.turnId, "turn-1");
590 assert.equal(firstEvent.value.payload.planMarkdown, "## Final plan\n\n- one\n- two");590 NodeAssert.equal(firstEvent.value.payload.planMarkdown, "## Final plan\n\n- one\n- two");
591 }),591 }),
592 );592 );
593593
...@@ -615,16 +615,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -615,16 +615,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
615615
616 const firstEvent = yield* Fiber.join(firstEventFiber);616 const firstEvent = yield* Fiber.join(firstEventFiber);
617617
618 assert.equal(firstEvent._tag, "Some");618 NodeAssert.equal(firstEvent._tag, "Some");
619 if (firstEvent._tag !== "Some") {619 if (firstEvent._tag !== "Some") {
620 return;620 return;
621 }621 }
622 assert.equal(firstEvent.value.type, "turn.proposed.delta");622 NodeAssert.equal(firstEvent.value.type, "turn.proposed.delta");
623 if (firstEvent.value.type !== "turn.proposed.delta") {623 if (firstEvent.value.type !== "turn.proposed.delta") {
624 return;624 return;
625 }625 }
626 assert.equal(firstEvent.value.turnId, "turn-1");626 NodeAssert.equal(firstEvent.value.turnId, "turn-1");
627 assert.equal(firstEvent.value.payload.delta, "## Final plan");627 NodeAssert.equal(firstEvent.value.payload.delta, "## Final plan");
628 }),628 }),
629 );629 );
630630
...@@ -646,16 +646,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -646,16 +646,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
646 yield* runtime.emit(event);646 yield* runtime.emit(event);
647 const firstEvent = yield* Fiber.join(firstEventFiber);647 const firstEvent = yield* Fiber.join(firstEventFiber);
648648
649 assert.equal(firstEvent._tag, "Some");649 NodeAssert.equal(firstEvent._tag, "Some");
650 if (firstEvent._tag !== "Some") {650 if (firstEvent._tag !== "Some") {
651 return;651 return;
652 }652 }
653 assert.equal(firstEvent.value.type, "session.exited");653 NodeAssert.equal(firstEvent.value.type, "session.exited");
654 if (firstEvent.value.type !== "session.exited") {654 if (firstEvent.value.type !== "session.exited") {
655 return;655 return;
656 }656 }
657 assert.equal(firstEvent.value.threadId, "thread-1");657 NodeAssert.equal(firstEvent.value.threadId, "thread-1");
658 assert.equal(firstEvent.value.payload.reason, "Session stopped");658 NodeAssert.equal(firstEvent.value.payload.reason, "Session stopped");
659 }),659 }),
660 );660 );
661661
...@@ -684,16 +684,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -684,16 +684,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
684684
685 const firstEvent = yield* Fiber.join(firstEventFiber);685 const firstEvent = yield* Fiber.join(firstEventFiber);
686686
687 assert.equal(firstEvent._tag, "Some");687 NodeAssert.equal(firstEvent._tag, "Some");
688 if (firstEvent._tag !== "Some") {688 if (firstEvent._tag !== "Some") {
689 return;689 return;
690 }690 }
691 assert.equal(firstEvent.value.type, "runtime.warning");691 NodeAssert.equal(firstEvent.value.type, "runtime.warning");
692 if (firstEvent.value.type !== "runtime.warning") {692 if (firstEvent.value.type !== "runtime.warning") {
693 return;693 return;
694 }694 }
695 assert.equal(firstEvent.value.turnId, "turn-1");695 NodeAssert.equal(firstEvent.value.turnId, "turn-1");
696 assert.equal(firstEvent.value.payload.message, "Reconnecting... 2/5");696 NodeAssert.equal(firstEvent.value.payload.message, "Reconnecting... 2/5");
697 }),697 }),
698 );698 );
699699
...@@ -715,16 +715,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -715,16 +715,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
715715
716 const firstEvent = yield* Fiber.join(firstEventFiber);716 const firstEvent = yield* Fiber.join(firstEventFiber);
717717
718 assert.equal(firstEvent._tag, "Some");718 NodeAssert.equal(firstEvent._tag, "Some");
719 if (firstEvent._tag !== "Some") {719 if (firstEvent._tag !== "Some") {
720 return;720 return;
721 }721 }
722 assert.equal(firstEvent.value.type, "runtime.warning");722 NodeAssert.equal(firstEvent.value.type, "runtime.warning");
723 if (firstEvent.value.type !== "runtime.warning") {723 if (firstEvent.value.type !== "runtime.warning") {
724 return;724 return;
725 }725 }
726 assert.equal(firstEvent.value.turnId, "turn-1");726 NodeAssert.equal(firstEvent.value.turnId, "turn-1");
727 assert.equal(727 NodeAssert.equal(
728 firstEvent.value.payload.message,728 firstEvent.value.payload.message,
729 "The filename or extension is too long. (os error 206)",729 "The filename or extension is too long. (os error 206)",
730 );730 );
...@@ -752,16 +752,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -752,16 +752,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
752752
753 const firstEvent = yield* Fiber.join(firstEventFiber);753 const firstEvent = yield* Fiber.join(firstEventFiber);
754754
755 assert.equal(firstEvent._tag, "Some");755 NodeAssert.equal(firstEvent._tag, "Some");
756 if (firstEvent._tag !== "Some") {756 if (firstEvent._tag !== "Some") {
757 return;757 return;
758 }758 }
759 assert.equal(firstEvent.value.type, "thread.realtime.started");759 NodeAssert.equal(firstEvent.value.type, "thread.realtime.started");
760 if (firstEvent.value.type !== "thread.realtime.started") {760 if (firstEvent.value.type !== "thread.realtime.started") {
761 return;761 return;
762 }762 }
763 assert.equal(firstEvent.value.threadId, "thread-1");763 NodeAssert.equal(firstEvent.value.threadId, "thread-1");
764 assert.equal(firstEvent.value.payload.realtimeSessionId, "realtime-session-1");764 NodeAssert.equal(firstEvent.value.payload.realtimeSessionId, "realtime-session-1");
765 }),765 }),
766 );766 );
767767
...@@ -784,17 +784,17 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -784,17 +784,17 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
784784
785 const firstEvent = yield* Fiber.join(firstEventFiber);785 const firstEvent = yield* Fiber.join(firstEventFiber);
786786
787 assert.equal(firstEvent._tag, "Some");787 NodeAssert.equal(firstEvent._tag, "Some");
788 if (firstEvent._tag !== "Some") {788 if (firstEvent._tag !== "Some") {
789 return;789 return;
790 }790 }
791 assert.equal(firstEvent.value.type, "runtime.error");791 NodeAssert.equal(firstEvent.value.type, "runtime.error");
792 if (firstEvent.value.type !== "runtime.error") {792 if (firstEvent.value.type !== "runtime.error") {
793 return;793 return;
794 }794 }
795 assert.equal(firstEvent.value.turnId, "turn-1");795 NodeAssert.equal(firstEvent.value.turnId, "turn-1");
796 assert.equal(firstEvent.value.payload.class, "provider_error");796 NodeAssert.equal(firstEvent.value.payload.class, "provider_error");
797 assert.equal(797 NodeAssert.equal(
798 firstEvent.value.payload.message,798 firstEvent.value.payload.message,
799 "2026-03-31T18:14:06.833399Z ERROR codex_api::endpoint::responses_websocket: failed to connect to websocket: HTTP error: 503 Service Unavailable, url: wss://chatgpt.com/backend-api/codex/responses",799 "2026-03-31T18:14:06.833399Z ERROR codex_api::endpoint::responses_websocket: failed to connect to websocket: HTTP error: 503 Service Unavailable, url: wss://chatgpt.com/backend-api/codex/responses",
800 );800 );
...@@ -824,15 +824,15 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -824,15 +824,15 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
824 yield* runtime.emit(event);824 yield* runtime.emit(event);
825 const firstEvent = yield* Fiber.join(firstEventFiber);825 const firstEvent = yield* Fiber.join(firstEventFiber);
826826
827 assert.equal(firstEvent._tag, "Some");827 NodeAssert.equal(firstEvent._tag, "Some");
828 if (firstEvent._tag !== "Some") {828 if (firstEvent._tag !== "Some") {
829 return;829 return;
830 }830 }
831 assert.equal(firstEvent.value.type, "request.resolved");831 NodeAssert.equal(firstEvent.value.type, "request.resolved");
832 if (firstEvent.value.type !== "request.resolved") {832 if (firstEvent.value.type !== "request.resolved") {
833 return;833 return;
834 }834 }
835 assert.equal(firstEvent.value.payload.requestType, "command_execution_approval");835 NodeAssert.equal(firstEvent.value.payload.requestType, "command_execution_approval");
836 }),836 }),
837 );837 );
838838
...@@ -859,15 +859,15 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -859,15 +859,15 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
859 yield* runtime.emit(event);859 yield* runtime.emit(event);
860 const firstEvent = yield* Fiber.join(firstEventFiber);860 const firstEvent = yield* Fiber.join(firstEventFiber);
861861
862 assert.equal(firstEvent._tag, "Some");862 NodeAssert.equal(firstEvent._tag, "Some");
863 if (firstEvent._tag !== "Some") {863 if (firstEvent._tag !== "Some") {
864 return;864 return;
865 }865 }
866 assert.equal(firstEvent.value.type, "request.resolved");866 NodeAssert.equal(firstEvent.value.type, "request.resolved");
867 if (firstEvent.value.type !== "request.resolved") {867 if (firstEvent.value.type !== "request.resolved") {
868 return;868 return;
869 }869 }
870 assert.equal(firstEvent.value.payload.requestType, "file_read_approval");870 NodeAssert.equal(firstEvent.value.payload.requestType, "file_read_approval");
871 }),871 }),
872 );872 );
873873
...@@ -895,15 +895,15 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -895,15 +895,15 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
895 yield* runtime.emit(event);895 yield* runtime.emit(event);
896 const firstEvent = yield* Fiber.join(firstEventFiber);896 const firstEvent = yield* Fiber.join(firstEventFiber);
897897
898 assert.equal(firstEvent._tag, "Some");898 NodeAssert.equal(firstEvent._tag, "Some");
899 if (firstEvent._tag !== "Some") {899 if (firstEvent._tag !== "Some") {
900 return;900 return;
901 }901 }
902 assert.equal(firstEvent.value.type, "user-input.resolved");902 NodeAssert.equal(firstEvent.value.type, "user-input.resolved");
903 if (firstEvent.value.type !== "user-input.resolved") {903 if (firstEvent.value.type !== "user-input.resolved") {
904 return;904 return;
905 }905 }
906 assert.deepEqual(firstEvent.value.payload.answers, {906 NodeAssert.deepEqual(firstEvent.value.payload.answers, {
907 scope: [],907 scope: [],
908 });908 });
909 }),909 }),
...@@ -934,20 +934,20 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -934,20 +934,20 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
934 yield* runtime.emit(event);934 yield* runtime.emit(event);
935 const events = Array.from(yield* Fiber.join(eventsFiber));935 const events = Array.from(yield* Fiber.join(eventsFiber));
936936
937 assert.equal(events.length, 2);937 NodeAssert.equal(events.length, 2);
938938
939 const firstEvent = events[0];939 const firstEvent = events[0];
940 const secondEvent = events[1];940 const secondEvent = events[1];
941941
942 assert.equal(firstEvent?.type, "session.state.changed");942 NodeAssert.equal(firstEvent?.type, "session.state.changed");
943 if (firstEvent?.type === "session.state.changed") {943 if (firstEvent?.type === "session.state.changed") {
944 assert.equal(firstEvent.payload.state, "error");944 NodeAssert.equal(firstEvent.payload.state, "error");
945 assert.equal(firstEvent.payload.reason, "Sandbox setup failed");945 NodeAssert.equal(firstEvent.payload.reason, "Sandbox setup failed");
946 }946 }
947947
948 assert.equal(secondEvent?.type, "runtime.warning");948 NodeAssert.equal(secondEvent?.type, "runtime.warning");
949 if (secondEvent?.type === "runtime.warning") {949 if (secondEvent?.type === "runtime.warning") {
950 assert.equal(secondEvent.payload.message, "Sandbox setup failed");950 NodeAssert.equal(secondEvent.payload.message, "Sandbox setup failed");
951 }951 }
952 }),952 }),
953 );953 );
...@@ -1006,17 +1006,17 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -1006,17 +1006,17 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
1006 } satisfies ProviderEvent);1006 } satisfies ProviderEvent);
10071007
1008 const events = Array.from(yield* Fiber.join(eventsFiber));1008 const events = Array.from(yield* Fiber.join(eventsFiber));
1009 assert.equal(events[0]?.type, "user-input.requested");1009 NodeAssert.equal(events[0]?.type, "user-input.requested");
1010 if (events[0]?.type === "user-input.requested") {1010 if (events[0]?.type === "user-input.requested") {
1011 assert.equal(events[0].requestId, "req-user-input-1");1011 NodeAssert.equal(events[0].requestId, "req-user-input-1");
1012 assert.equal(events[0].payload.questions[0]?.id, "sandbox_mode");1012 NodeAssert.equal(events[0].payload.questions[0]?.id, "sandbox_mode");
1013 assert.equal(events[0].payload.questions[0]?.multiSelect, false);1013 NodeAssert.equal(events[0].payload.questions[0]?.multiSelect, false);
1014 }1014 }
10151015
1016 assert.equal(events[1]?.type, "user-input.resolved");1016 NodeAssert.equal(events[1]?.type, "user-input.resolved");
1017 if (events[1]?.type === "user-input.resolved") {1017 if (events[1]?.type === "user-input.resolved") {
1018 assert.equal(events[1].requestId, "req-user-input-1");1018 NodeAssert.equal(events[1].requestId, "req-user-input-1");
1019 assert.deepEqual(events[1].payload.answers, {1019 NodeAssert.deepEqual(events[1].payload.answers, {
1020 sandbox_mode: "workspace-write",1020 sandbox_mode: "workspace-write",
1021 });1021 });
1022 }1022 }
...@@ -1060,16 +1060,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -1060,16 +1060,16 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
1060 } satisfies ProviderEvent);1060 } satisfies ProviderEvent);
10611061
1062 const firstEvent = yield* Fiber.join(firstEventFiber);1062 const firstEvent = yield* Fiber.join(firstEventFiber);
1063 assert.equal(firstEvent._tag, "Some");1063 NodeAssert.equal(firstEvent._tag, "Some");
1064 if (firstEvent._tag !== "Some") {1064 if (firstEvent._tag !== "Some") {
1065 return;1065 return;
1066 }1066 }
1067 assert.equal(firstEvent.value.type, "thread.token-usage.updated");1067 NodeAssert.equal(firstEvent.value.type, "thread.token-usage.updated");
1068 if (firstEvent.value.type !== "thread.token-usage.updated") {1068 if (firstEvent.value.type !== "thread.token-usage.updated") {
1069 return;1069 return;
1070 }1070 }
10711071
1072 assert.deepEqual(firstEvent.value.payload.usage, {1072 NodeAssert.deepEqual(firstEvent.value.payload.usage, {
1073 usedTokens: 126,1073 usedTokens: 126,
1074 totalProcessedTokens: 11_839,1074 totalProcessedTokens: 11_839,
1075 maxTokens: 258_400,1075 maxTokens: 258_400,
...@@ -1119,15 +1119,15 @@ scopedLifecycleLayer("CodexAdapterLive scoped lifecycle", (it) => {...@@ -1119,15 +1119,15 @@ scopedLifecycleLayer("CodexAdapterLive scoped lifecycle", (it) => {
1119 });1119 });
11201120
1121 const runtime = scopedLifecycleRuntimeFactory.lastRuntime;1121 const runtime = scopedLifecycleRuntimeFactory.lastRuntime;
1122 assert.ok(runtime);1122 NodeAssert.ok(runtime);
11231123
1124 yield* adapter.stopSession(asThreadId("thread-stop"));1124 yield* adapter.stopSession(asThreadId("thread-stop"));
11251125
1126 assert.equal(runtime.closeImpl.mock.calls.length, 1);1126 NodeAssert.equal(runtime.closeImpl.mock.calls.length, 1);
1127 assert.deepStrictEqual(scopedLifecycleRuntimeFactory.releasedThreadIds, [1127 NodeAssert.deepStrictEqual(scopedLifecycleRuntimeFactory.releasedThreadIds, [
1128 asThreadId("thread-stop"),1128 asThreadId("thread-stop"),
1129 ]);1129 ]);
1130 assert.equal(yield* adapter.hasSession(asThreadId("thread-stop")), false);1130 NodeAssert.equal(yield* adapter.hasSession(asThreadId("thread-stop")), false);
1131 }),1131 }),
1132 );1132 );
1133});1133});
...@@ -1164,20 +1164,22 @@ scopedFailureLayer("CodexAdapterLive scoped startup failure", (it) => {...@@ -1164,20 +1164,22 @@ scopedFailureLayer("CodexAdapterLive scoped startup failure", (it) => {
1164 })1164 })
1165 .pipe(Effect.result);1165 .pipe(Effect.result);
11661166
1167 assert.equal(result._tag, "Failure");1167 NodeAssert.equal(result._tag, "Failure");
1168 assert.equal(result.failure._tag, "ProviderAdapterProcessError");1168 NodeAssert.equal(result.failure._tag, "ProviderAdapterProcessError");
1169 assert.deepStrictEqual(scopedFailureRuntimeFactory.releasedThreadIds, [1169 NodeAssert.deepStrictEqual(scopedFailureRuntimeFactory.releasedThreadIds, [
1170 asThreadId("thread-fail"),1170 asThreadId("thread-fail"),
1171 ]);1171 ]);
1172 assert.equal(yield* adapter.hasSession(asThreadId("thread-fail")), false);1172 NodeAssert.equal(yield* adapter.hasSession(asThreadId("thread-fail")), false);
1173 }),1173 }),
1174 );1174 );
1175});1175});
11761176
1177it.effect("flushes managed native logs when the adapter layer shuts down", () =>1177it.effect("flushes managed native logs when the adapter layer shuts down", () =>
1178 Effect.gen(function* () {1178 Effect.gen(function* () {
1179 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-codex-adapter-native-log-"));1179 const tempDir = NodeFS.mkdtempSync(
1180 const basePath = path.join(tempDir, "provider-native.ndjson");1180 NodePath.join(NodeOS.tmpdir(), "t3-codex-adapter-native-log-"),
1181 );
1182 const basePath = NodePath.join(tempDir, "provider-native.ndjson");
1181 const runtimeFactory = makeRuntimeFactory();1183 const runtimeFactory = makeRuntimeFactory();
1182 const scope = yield* Scope.make("sequential");1184 const scope = yield* Scope.make("sequential");
1183 let scopeClosed = false;1185 let scopeClosed = false;
...@@ -1208,7 +1210,7 @@ it.effect("flushes managed native logs when the adapter layer shuts down", () =>...@@ -1208,7 +1210,7 @@ it.effect("flushes managed native logs when the adapter layer shuts down", () =>
1208 });1210 });
12091211
1210 const runtime = runtimeFactory.lastRuntime;1212 const runtime = runtimeFactory.lastRuntime;
1211 assert.ok(runtime);1213 NodeAssert.ok(runtime);
12121214
1213 const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild);1215 const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild);
1214 yield* runtime.emit({1216 yield* runtime.emit({
...@@ -1225,15 +1227,15 @@ it.effect("flushes managed native logs when the adapter layer shuts down", () =>...@@ -1225,15 +1227,15 @@ it.effect("flushes managed native logs when the adapter layer shuts down", () =>
1225 yield* Scope.close(scope, Exit.void);1227 yield* Scope.close(scope, Exit.void);
1226 scopeClosed = true;1228 scopeClosed = true;
12271229
1228 const threadLogPath = path.join(tempDir, "thread-logger.log");1230 const threadLogPath = NodePath.join(tempDir, "thread-logger.log");
1229 assert.equal(fs.existsSync(threadLogPath), true);1231 NodeAssert.equal(NodeFS.existsSync(threadLogPath), true);
1230 const contents = fs.readFileSync(threadLogPath, "utf8");1232 const contents = NodeFS.readFileSync(threadLogPath, "utf8");
1231 assert.match(contents, /NTIVE: .*"message":"native flush test"/);1233 NodeAssert.match(contents, /NTIVE: .*"message":"native flush test"/);
1232 } finally {1234 } finally {
1233 if (!scopeClosed) {1235 if (!scopeClosed) {
1234 yield* Scope.close(scope, Exit.void);1236 yield* Scope.close(scope, Exit.void);
1235 }1237 }
1236 fs.rmSync(tempDir, { recursive: true, force: true });1238 NodeFS.rmSync(tempDir, { recursive: true, force: true });
1237 }1239 }
1238 }),1240 }),
1239);1241);
apps/server/src/provider/Layers/CodexSessionRuntime.test.ts+18-18
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import assert from "node:assert/strict";1import * as NodeAssert from "node:assert/strict";
22
3import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
4import * as Schema from "effect/Schema";4import * as Schema from "effect/Schema";
...@@ -55,7 +55,7 @@ describe("buildTurnStartParams", () => {...@@ -55,7 +55,7 @@ describe("buildTurnStartParams", () => {
55 }),55 }),
56 );56 );
5757
58 assert.deepStrictEqual(params, {58 NodeAssert.deepStrictEqual(params, {
59 threadId: "provider-thread-1",59 threadId: "provider-thread-1",
60 approvalPolicy: "never",60 approvalPolicy: "never",
61 sandboxPolicy: {61 sandboxPolicy: {
...@@ -97,7 +97,7 @@ describe("buildTurnStartParams", () => {...@@ -97,7 +97,7 @@ describe("buildTurnStartParams", () => {
97 }),97 }),
98 );98 );
9999
100 assert.deepStrictEqual(params, {100 NodeAssert.deepStrictEqual(params, {
101 threadId: "provider-thread-1",101 threadId: "provider-thread-1",
102 approvalPolicy: "on-request",102 approvalPolicy: "on-request",
103 sandboxPolicy: {103 sandboxPolicy: {
...@@ -134,7 +134,7 @@ describe("buildTurnStartParams", () => {...@@ -134,7 +134,7 @@ describe("buildTurnStartParams", () => {
134 }),134 }),
135 );135 );
136136
137 assert.deepStrictEqual(params, {137 NodeAssert.deepStrictEqual(params, {
138 threadId: "provider-thread-1",138 threadId: "provider-thread-1",
139 approvalPolicy: "untrusted",139 approvalPolicy: "untrusted",
140 sandboxPolicy: {140 sandboxPolicy: {
...@@ -156,19 +156,19 @@ describe("T3 browser developer instructions", () => {...@@ -156,19 +156,19 @@ describe("T3 browser developer instructions", () => {
156 CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,156 CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
157 CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,157 CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,
158 ]) {158 ]) {
159 assert.match(instructions, /t3-code/);159 NodeAssert.match(instructions, /t3-code/);
160 assert.match(instructions, /preview_status/);160 NodeAssert.match(instructions, /preview_status/);
161 assert.match(instructions, /preview_open/);161 NodeAssert.match(instructions, /preview_open/);
162 assert.match(instructions, /Do not switch to global browser skills/);162 NodeAssert.match(instructions, /Do not switch to global browser skills/);
163 }163 }
164 });164 });
165});165});
166166
167describe("hasConfiguredMcpServer", () => {167describe("hasConfiguredMcpServer", () => {
168 it("detects inline Codex MCP configuration arguments", () => {168 it("detects inline Codex MCP configuration arguments", () => {
169 assert.equal(hasConfiguredMcpServer(undefined), false);169 NodeAssert.equal(hasConfiguredMcpServer(undefined), false);
170 assert.equal(hasConfiguredMcpServer(["--model", "gpt-5.4"]), false);170 NodeAssert.equal(hasConfiguredMcpServer(["--model", "gpt-5.4"]), false);
171 assert.equal(171 NodeAssert.equal(
172 hasConfiguredMcpServer(["-c", 'mcp_servers.t3-code.url="http://127.0.0.1/mcp"']),172 hasConfiguredMcpServer(["-c", 'mcp_servers.t3-code.url="http://127.0.0.1/mcp"']),
173 true,173 true,
174 );174 );
...@@ -177,7 +177,7 @@ describe("hasConfiguredMcpServer", () => {...@@ -177,7 +177,7 @@ describe("hasConfiguredMcpServer", () => {
177177
178describe("isRecoverableThreadResumeError", () => {178describe("isRecoverableThreadResumeError", () => {
179 it("matches missing thread errors", () => {179 it("matches missing thread errors", () => {
180 assert.equal(180 NodeAssert.equal(
181 isRecoverableThreadResumeError(181 isRecoverableThreadResumeError(
182 new CodexErrors.CodexAppServerRequestError({182 new CodexErrors.CodexAppServerRequestError({
183 code: -32603,183 code: -32603,
...@@ -189,7 +189,7 @@ describe("isRecoverableThreadResumeError", () => {...@@ -189,7 +189,7 @@ describe("isRecoverableThreadResumeError", () => {
189 });189 });
190190
191 it("ignores non-recoverable resume errors", () => {191 it("ignores non-recoverable resume errors", () => {
192 assert.equal(192 NodeAssert.equal(
193 isRecoverableThreadResumeError(193 isRecoverableThreadResumeError(
194 new CodexErrors.CodexAppServerRequestError({194 new CodexErrors.CodexAppServerRequestError({
195 code: -32603,195 code: -32603,
...@@ -201,7 +201,7 @@ describe("isRecoverableThreadResumeError", () => {...@@ -201,7 +201,7 @@ describe("isRecoverableThreadResumeError", () => {
201 });201 });
202202
203 it("ignores unrelated missing-resource errors that do not mention threads", () => {203 it("ignores unrelated missing-resource errors that do not mention threads", () => {
204 assert.equal(204 NodeAssert.equal(
205 isRecoverableThreadResumeError(205 isRecoverableThreadResumeError(
206 new CodexErrors.CodexAppServerRequestError({206 new CodexErrors.CodexAppServerRequestError({
207 code: -32603,207 code: -32603,
...@@ -210,7 +210,7 @@ describe("isRecoverableThreadResumeError", () => {...@@ -210,7 +210,7 @@ describe("isRecoverableThreadResumeError", () => {
210 ),210 ),
211 false,211 false,
212 );212 );
213 assert.equal(213 NodeAssert.equal(
214 isRecoverableThreadResumeError(214 isRecoverableThreadResumeError(
215 new CodexErrors.CodexAppServerRequestError({215 new CodexErrors.CodexAppServerRequestError({
216 code: -32603,216 code: -32603,
...@@ -256,8 +256,8 @@ describe("openCodexThread", () => {...@@ -256,8 +256,8 @@ describe("openCodexThread", () => {
256 }),256 }),
257 );257 );
258258
259 assert.equal(opened.thread.id, "fresh-thread");259 NodeAssert.equal(opened.thread.id, "fresh-thread");
260 assert.deepStrictEqual(260 NodeAssert.deepStrictEqual(
261 calls.map((call) => call.method),261 calls.map((call) => call.method),
262 ["thread/resume", "thread/start"],262 ["thread/resume", "thread/start"],
263 );263 );
...@@ -283,7 +283,7 @@ describe("openCodexThread", () => {...@@ -283,7 +283,7 @@ describe("openCodexThread", () => {
283 },283 },
284 };284 };
285285
286 await assert.rejects(286 await NodeAssert.rejects(
287 Effect.runPromise(287 Effect.runPromise(
288 openCodexThread({288 openCodexThread({
289 client,289 client,
apps/server/src/provider/Layers/CursorAdapter.test.ts+63-49
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import * as path from "node:path";2import * as NodePath from "node:path";
3import * as os from "node:os";3import * as NodeOS from "node:os";
4import { chmod, mkdtemp, readFile, writeFile } from "node:fs/promises";4import * as NodeFSP from "node:fs/promises";
5import { fileURLToPath } from "node:url";5import * as NodeURL from "node:url";
66
7import * as NodeServices from "@effect/platform-node/NodeServices";7import * as NodeServices from "@effect/platform-node/NodeServices";
8import { assert, it } from "@effect/vitest";8import { assert, it } from "@effect/vitest";
...@@ -36,8 +36,8 @@ class CursorAdapter extends Context.Service<CursorAdapter, CursorAdapterShape>()...@@ -36,8 +36,8 @@ class CursorAdapter extends Context.Service<CursorAdapter, CursorAdapterShape>()
36 "t3/provider/Layers/CursorAdapter.test/CursorAdapter",36 "t3/provider/Layers/CursorAdapter.test/CursorAdapter",
37) {}37) {}
3838
39const __dirname = path.dirname(fileURLToPath(import.meta.url));39const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
40const mockAgentPath = path.join(__dirname, "../../../scripts/acp-mock-agent.ts");40const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.ts");
41const mockAgentCommand = "node";41const mockAgentCommand = "node";
42const mockAgentArgs = [mockAgentPath] as const;42const mockAgentArgs = [mockAgentPath] as const;
4343
...@@ -45,8 +45,8 @@ async function makeMockAgentWrapper(...@@ -45,8 +45,8 @@ async function makeMockAgentWrapper(
45 extraEnv?: Record<string, string>,45 extraEnv?: Record<string, string>,
46 options?: { initialDelaySeconds?: number },46 options?: { initialDelaySeconds?: number },
47) {47) {
48 const dir = await mkdtemp(path.join(os.tmpdir(), "cursor-acp-mock-"));48 const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-mock-"));
49 const wrapperPath = path.join(dir, "fake-agent.sh");49 const wrapperPath = NodePath.join(dir, "fake-agent.sh");
50 const envExports = Object.entries(extraEnv ?? {})50 const envExports = Object.entries(extraEnv ?? {})
51 .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)51 .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
52 .join("\n");52 .join("\n");
...@@ -55,8 +55,8 @@ ${envExports}...@@ -55,8 +55,8 @@ ${envExports}
55${options?.initialDelaySeconds ? `sleep ${JSON.stringify(String(options.initialDelaySeconds))}` : ""}55${options?.initialDelaySeconds ? `sleep ${JSON.stringify(String(options.initialDelaySeconds))}` : ""}
56exec ${JSON.stringify(mockAgentCommand)} ${mockAgentArgs.map((arg) => JSON.stringify(arg)).join(" ")} "$@"56exec ${JSON.stringify(mockAgentCommand)} ${mockAgentArgs.map((arg) => JSON.stringify(arg)).join(" ")} "$@"
57`;57`;
58 await writeFile(wrapperPath, script, "utf8");58 await NodeFSP.writeFile(wrapperPath, script, "utf8");
59 await chmod(wrapperPath, 0o755);59 await NodeFSP.chmod(wrapperPath, 0o755);
60 return wrapperPath;60 return wrapperPath;
61}61}
6262
...@@ -65,8 +65,8 @@ async function makeProbeWrapper(...@@ -65,8 +65,8 @@ async function makeProbeWrapper(
65 argvLogPath: string,65 argvLogPath: string,
66 extraEnv?: Record<string, string>,66 extraEnv?: Record<string, string>,
67) {67) {
68 const dir = await mkdtemp(path.join(os.tmpdir(), "cursor-acp-probe-"));68 const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-probe-"));
69 const wrapperPath = path.join(dir, "fake-agent.sh");69 const wrapperPath = NodePath.join(dir, "fake-agent.sh");
70 const envExports = Object.entries(extraEnv ?? {})70 const envExports = Object.entries(extraEnv ?? {})
71 .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)71 .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
72 .join("\n");72 .join("\n");
...@@ -77,13 +77,13 @@ export T3_ACP_REQUEST_LOG_PATH=${JSON.stringify(requestLogPath)}...@@ -77,13 +77,13 @@ export T3_ACP_REQUEST_LOG_PATH=${JSON.stringify(requestLogPath)}
77${envExports}77${envExports}
78exec ${JSON.stringify(mockAgentCommand)} ${mockAgentArgs.map((arg) => JSON.stringify(arg)).join(" ")} "$@"78exec ${JSON.stringify(mockAgentCommand)} ${mockAgentArgs.map((arg) => JSON.stringify(arg)).join(" ")} "$@"
79`;79`;
80 await writeFile(wrapperPath, script, "utf8");80 await NodeFSP.writeFile(wrapperPath, script, "utf8");
81 await chmod(wrapperPath, 0o755);81 await NodeFSP.chmod(wrapperPath, 0o755);
82 return wrapperPath;82 return wrapperPath;
83}83}
8484
85async function readArgvLog(filePath: string) {85async function readArgvLog(filePath: string) {
86 const raw = await readFile(filePath, "utf8");86 const raw = await NodeFSP.readFile(filePath, "utf8");
87 return raw87 return raw
88 .split("\n")88 .split("\n")
89 .map((line) => line.trim())89 .map((line) => line.trim())
...@@ -92,7 +92,7 @@ async function readArgvLog(filePath: string) {...@@ -92,7 +92,7 @@ async function readArgvLog(filePath: string) {
92}92}
9393
94async function readJsonLines(filePath: string) {94async function readJsonLines(filePath: string) {
95 const raw = await readFile(filePath, "utf8");95 const raw = await NodeFSP.readFile(filePath, "utf8");
96 return raw96 return raw
97 .split("\n")97 .split("\n")
98 .map((line) => line.trim())98 .map((line) => line.trim())
...@@ -103,7 +103,7 @@ async function readJsonLines(filePath: string) {...@@ -103,7 +103,7 @@ async function readJsonLines(filePath: string) {
103async function waitForFileContent(filePath: string, attempts = 40) {103async function waitForFileContent(filePath: string, attempts = 40) {
104 for (let attempt = 0; attempt < attempts; attempt += 1) {104 for (let attempt = 0; attempt < attempts; attempt += 1) {
105 try {105 try {
106 const raw = await readFile(filePath, "utf8");106 const raw = await NodeFSP.readFile(filePath, "utf8");
107 if (raw.trim().length > 0) {107 if (raw.trim().length > 0) {
108 return raw;108 return raw;
109 }109 }
...@@ -315,9 +315,9 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -315,9 +315,9 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
315 const settings = yield* ServerSettingsService;315 const settings = yield* ServerSettingsService;
316 const threadId = ThreadId.make("cursor-stop-session-close");316 const threadId = ThreadId.make("cursor-stop-session-close");
317 const tempDir = yield* Effect.promise(() =>317 const tempDir = yield* Effect.promise(() =>
318 mkdtemp(path.join(os.tmpdir(), "cursor-adapter-exit-log-")),318 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-adapter-exit-log-")),
319 );319 );
320 const exitLogPath = path.join(tempDir, "exit.log");320 const exitLogPath = NodePath.join(tempDir, "exit.log");
321321
322 const wrapperPath = yield* Effect.promise(() =>322 const wrapperPath = yield* Effect.promise(() =>
323 makeMockAgentWrapper({323 makeMockAgentWrapper({
...@@ -349,9 +349,9 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -349,9 +349,9 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
349 const settings = yield* ServerSettingsService;349 const settings = yield* ServerSettingsService;
350 const threadId = ThreadId.make("cursor-concurrent-start-session");350 const threadId = ThreadId.make("cursor-concurrent-start-session");
351 const tempDir = yield* Effect.promise(() =>351 const tempDir = yield* Effect.promise(() =>
352 mkdtemp(path.join(os.tmpdir(), "cursor-adapter-concurrent-exit-log-")),352 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-adapter-concurrent-exit-log-")),
353 );353 );
354 const exitLogPath = path.join(tempDir, "exit.log");354 const exitLogPath = NodePath.join(tempDir, "exit.log");
355355
356 const wrapperPath = yield* Effect.promise(() =>356 const wrapperPath = yield* Effect.promise(() =>
357 makeMockAgentWrapper(357 makeMockAgentWrapper(
...@@ -414,10 +414,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -414,10 +414,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
414 const adapter = yield* CursorAdapter;414 const adapter = yield* CursorAdapter;
415 const serverSettings = yield* ServerSettingsService;415 const serverSettings = yield* ServerSettingsService;
416 const threadId = ThreadId.make("cursor-plan-mode-probe");416 const threadId = ThreadId.make("cursor-plan-mode-probe");
417 const tempDir = yield* Effect.promise(() => mkdtemp(path.join(os.tmpdir(), "cursor-acp-")));417 const tempDir = yield* Effect.promise(() =>
418 const requestLogPath = path.join(tempDir, "requests.ndjson");418 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-")),
419 const argvLogPath = path.join(tempDir, "argv.txt");419 );
420 yield* Effect.promise(() => writeFile(requestLogPath, "", "utf8"));420 const requestLogPath = NodePath.join(tempDir, "requests.ndjson");
421 const argvLogPath = NodePath.join(tempDir, "argv.txt");
422 yield* Effect.promise(() => NodeFSP.writeFile(requestLogPath, "", "utf8"));
421 const wrapperPath = yield* Effect.promise(() =>423 const wrapperPath = yield* Effect.promise(() =>
422 makeProbeWrapper(requestLogPath, argvLogPath),424 makeProbeWrapper(requestLogPath, argvLogPath),
423 );425 );
...@@ -470,10 +472,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -470,10 +472,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
470 const adapter = yield* CursorAdapter;472 const adapter = yield* CursorAdapter;
471 const serverSettings = yield* ServerSettingsService;473 const serverSettings = yield* ServerSettingsService;
472 const threadId = ThreadId.make("cursor-initial-config-probe");474 const threadId = ThreadId.make("cursor-initial-config-probe");
473 const tempDir = yield* Effect.promise(() => mkdtemp(path.join(os.tmpdir(), "cursor-acp-")));475 const tempDir = yield* Effect.promise(() =>
474 const requestLogPath = path.join(tempDir, "requests.ndjson");476 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-")),
475 const argvLogPath = path.join(tempDir, "argv.txt");477 );
476 yield* Effect.promise(() => writeFile(requestLogPath, "", "utf8"));478 const requestLogPath = NodePath.join(tempDir, "requests.ndjson");
479 const argvLogPath = NodePath.join(tempDir, "argv.txt");
480 yield* Effect.promise(() => NodeFSP.writeFile(requestLogPath, "", "utf8"));
477 const wrapperPath = yield* Effect.promise(() =>481 const wrapperPath = yield* Effect.promise(() =>
478 makeProbeWrapper(requestLogPath, argvLogPath),482 makeProbeWrapper(requestLogPath, argvLogPath),
479 );483 );
...@@ -713,10 +717,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -713,10 +717,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
713 const runtimeEvents: Array<ProviderRuntimeEvent> = [];717 const runtimeEvents: Array<ProviderRuntimeEvent> = [];
714 const settledEventTypes = new Set<string>();718 const settledEventTypes = new Set<string>();
715 const settledEventsReady = yield* Deferred.make<void>();719 const settledEventsReady = yield* Deferred.make<void>();
716 const tempDir = yield* Effect.promise(() => mkdtemp(path.join(os.tmpdir(), "cursor-acp-")));720 const tempDir = yield* Effect.promise(() =>
717 const requestLogPath = path.join(tempDir, "requests.ndjson");721 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-")),
718 const argvLogPath = path.join(tempDir, "argv.txt");722 );
719 yield* Effect.promise(() => writeFile(requestLogPath, "", "utf8"));723 const requestLogPath = NodePath.join(tempDir, "requests.ndjson");
724 const argvLogPath = NodePath.join(tempDir, "argv.txt");
725 yield* Effect.promise(() => NodeFSP.writeFile(requestLogPath, "", "utf8"));
720 const wrapperPath = yield* Effect.promise(() =>726 const wrapperPath = yield* Effect.promise(() =>
721 makeProbeWrapper(requestLogPath, argvLogPath, { T3_ACP_EMIT_TOOL_CALLS: "1" }),727 makeProbeWrapper(requestLogPath, argvLogPath, { T3_ACP_EMIT_TOOL_CALLS: "1" }),
722 );728 );
...@@ -931,10 +937,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -931,10 +937,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
931 const adapter = yield* CursorAdapter;937 const adapter = yield* CursorAdapter;
932 const serverSettings = yield* ServerSettingsService;938 const serverSettings = yield* ServerSettingsService;
933 const threadId = ThreadId.make("cursor-cancel-probe");939 const threadId = ThreadId.make("cursor-cancel-probe");
934 const tempDir = yield* Effect.promise(() => mkdtemp(path.join(os.tmpdir(), "cursor-acp-")));940 const tempDir = yield* Effect.promise(() =>
935 const requestLogPath = path.join(tempDir, "requests.ndjson");941 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-")),
936 const argvLogPath = path.join(tempDir, "argv.txt");942 );
937 yield* Effect.promise(() => writeFile(requestLogPath, "", "utf8"));943 const requestLogPath = NodePath.join(tempDir, "requests.ndjson");
944 const argvLogPath = NodePath.join(tempDir, "argv.txt");
945 yield* Effect.promise(() => NodeFSP.writeFile(requestLogPath, "", "utf8"));
938 const wrapperPath = yield* Effect.promise(() =>946 const wrapperPath = yield* Effect.promise(() =>
939 makeProbeWrapper(requestLogPath, argvLogPath, { T3_ACP_EMIT_TOOL_CALLS: "1" }),947 makeProbeWrapper(requestLogPath, argvLogPath, { T3_ACP_EMIT_TOOL_CALLS: "1" }),
940 );948 );
...@@ -1192,10 +1200,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -1192,10 +1200,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
1192 const adapter = yield* CursorAdapter;1200 const adapter = yield* CursorAdapter;
1193 const serverSettings = yield* ServerSettingsService;1201 const serverSettings = yield* ServerSettingsService;
1194 const threadId = ThreadId.make("cursor-model-switch");1202 const threadId = ThreadId.make("cursor-model-switch");
1195 const tempDir = yield* Effect.promise(() => mkdtemp(path.join(os.tmpdir(), "cursor-acp-")));1203 const tempDir = yield* Effect.promise(() =>
1196 const requestLogPath = path.join(tempDir, "requests.ndjson");1204 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-")),
1197 const argvLogPath = path.join(tempDir, "argv.txt");1205 );
1198 yield* Effect.promise(() => writeFile(requestLogPath, "", "utf8"));1206 const requestLogPath = NodePath.join(tempDir, "requests.ndjson");
1207 const argvLogPath = NodePath.join(tempDir, "argv.txt");
1208 yield* Effect.promise(() => NodeFSP.writeFile(requestLogPath, "", "utf8"));
1199 const wrapperPath = yield* Effect.promise(() =>1209 const wrapperPath = yield* Effect.promise(() =>
1200 makeProbeWrapper(requestLogPath, argvLogPath),1210 makeProbeWrapper(requestLogPath, argvLogPath),
1201 );1211 );
...@@ -1255,10 +1265,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -1255,10 +1265,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
1255 const adapter = yield* CursorAdapter;1265 const adapter = yield* CursorAdapter;
1256 const serverSettings = yield* ServerSettingsService;1266 const serverSettings = yield* ServerSettingsService;
1257 const threadId = ThreadId.make("cursor-fast-mode-reset");1267 const threadId = ThreadId.make("cursor-fast-mode-reset");
1258 const tempDir = yield* Effect.promise(() => mkdtemp(path.join(os.tmpdir(), "cursor-acp-")));1268 const tempDir = yield* Effect.promise(() =>
1259 const requestLogPath = path.join(tempDir, "requests.ndjson");1269 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-")),
1260 const argvLogPath = path.join(tempDir, "argv.txt");1270 );
1261 yield* Effect.promise(() => writeFile(requestLogPath, "", "utf8"));1271 const requestLogPath = NodePath.join(tempDir, "requests.ndjson");
1272 const argvLogPath = NodePath.join(tempDir, "argv.txt");
1273 yield* Effect.promise(() => NodeFSP.writeFile(requestLogPath, "", "utf8"));
1262 const wrapperPath = yield* Effect.promise(() =>1274 const wrapperPath = yield* Effect.promise(() =>
1263 makeProbeWrapper(requestLogPath, argvLogPath),1275 makeProbeWrapper(requestLogPath, argvLogPath),
1264 );1276 );
...@@ -1339,10 +1351,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {...@@ -1339,10 +1351,12 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => {
1339 const adapter = yield* CursorAdapter;1351 const adapter = yield* CursorAdapter;
1340 const serverSettings = yield* ServerSettingsService;1352 const serverSettings = yield* ServerSettingsService;
1341 const threadId = ThreadId.make("cursor-fast-mode-custom-instance");1353 const threadId = ThreadId.make("cursor-fast-mode-custom-instance");
1342 const tempDir = yield* Effect.promise(() => mkdtemp(path.join(os.tmpdir(), "cursor-acp-")));1354 const tempDir = yield* Effect.promise(() =>
1343 const requestLogPath = path.join(tempDir, "requests.ndjson");1355 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-")),
1344 const argvLogPath = path.join(tempDir, "argv.txt");1356 );
1345 yield* Effect.promise(() => writeFile(requestLogPath, "", "utf8"));1357 const requestLogPath = NodePath.join(tempDir, "requests.ndjson");
1358 const argvLogPath = NodePath.join(tempDir, "argv.txt");
1359 yield* Effect.promise(() => NodeFSP.writeFile(requestLogPath, "", "utf8"));
1346 const wrapperPath = yield* Effect.promise(() =>1360 const wrapperPath = yield* Effect.promise(() =>
1347 makeProbeWrapper(requestLogPath, argvLogPath),1361 makeProbeWrapper(requestLogPath, argvLogPath),
1348 );1362 );
apps/server/src/provider/Layers/CursorProvider.ts+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import * as NodeOs from "node:os";1import * as NodeOS from "node:os";
2import type {2import type {
3 CursorSettings,3 CursorSettings,
4 ModelCapabilities,4 ModelCapabilities,
...@@ -746,7 +746,7 @@ function isCursorAboutJsonFormatUnsupported(result: CommandResult): boolean {...@@ -746,7 +746,7 @@ function isCursorAboutJsonFormatUnsupported(result: CommandResult): boolean {
746const readCursorCliConfigChannel = Effect.fn("readCursorCliConfigChannel")(function* () {746const readCursorCliConfigChannel = Effect.fn("readCursorCliConfigChannel")(function* () {
747 const fileSystem = yield* FileSystem.FileSystem;747 const fileSystem = yield* FileSystem.FileSystem;
748 const path = yield* Path.Path;748 const path = yield* Path.Path;
749 const configPath = path.join(NodeOs.homedir(), ".cursor", "cli-config.json");749 const configPath = path.join(NodeOS.homedir(), ".cursor", "cli-config.json");
750 const raw = yield* fileSystem.readFileString(configPath).pipe(Effect.orElseSucceed(() => ""));750 const raw = yield* fileSystem.readFileString(configPath).pipe(Effect.orElseSucceed(() => ""));
751 return parseCursorCliConfigChannel(raw);751 return parseCursorCliConfigChannel(raw);
752});752});
apps/server/src/provider/Layers/EventNdjsonLogger.test.ts+28-31
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import os from "node:os";3import * as NodeOS from "node:os";
4import path from "node:path";4import * as NodePath from "node:path";
55
6import { ThreadId } from "@t3tools/contracts";6import { ThreadId } from "@t3tools/contracts";
7import { assert, describe, it } from "@effect/vitest";7import { assert, describe, it } from "@effect/vitest";
...@@ -31,8 +31,8 @@ function parseLogLine(line: string) {...@@ -31,8 +31,8 @@ function parseLogLine(line: string) {
31describe("EventNdjsonLogger", () => {31describe("EventNdjsonLogger", () => {
32 it.effect("writes effect-style lines to thread-scoped files", () =>32 it.effect("writes effect-style lines to thread-scoped files", () =>
33 Effect.gen(function* () {33 Effect.gen(function* () {
34 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-log-"));34 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-log-"));
35 const basePath = path.join(tempDir, "provider-native.ndjson");35 const basePath = NodePath.join(tempDir, "provider-native.ndjson");
3636
37 try {37 try {
38 const logger = yield* makeEventNdjsonLogger(basePath, { stream: "native" });38 const logger = yield* makeEventNdjsonLogger(basePath, { stream: "native" });
...@@ -51,13 +51,13 @@ describe("EventNdjsonLogger", () => {...@@ -51,13 +51,13 @@ describe("EventNdjsonLogger", () => {
51 );51 );
52 yield* logger.close();52 yield* logger.close();
5353
54 const threadOnePath = path.join(tempDir, "thread-1.log");54 const threadOnePath = NodePath.join(tempDir, "thread-1.log");
55 const threadTwoPath = path.join(tempDir, "thread-2.log");55 const threadTwoPath = NodePath.join(tempDir, "thread-2.log");
56 assert.equal(fs.existsSync(threadOnePath), true);56 assert.equal(NodeFS.existsSync(threadOnePath), true);
57 assert.equal(fs.existsSync(threadTwoPath), true);57 assert.equal(NodeFS.existsSync(threadTwoPath), true);
5858
59 const first = parseLogLine(fs.readFileSync(threadOnePath, "utf8").trim());59 const first = parseLogLine(NodeFS.readFileSync(threadOnePath, "utf8").trim());
60 const second = parseLogLine(fs.readFileSync(threadTwoPath, "utf8").trim());60 const second = parseLogLine(NodeFS.readFileSync(threadTwoPath, "utf8").trim());
6161
62 assert.equal(Number.isNaN(Date.parse(first.observedAt)), false);62 assert.equal(Number.isNaN(Date.parse(first.observedAt)), false);
63 assert.equal(first.stream, "NTIVE");63 assert.equal(first.stream, "NTIVE");
...@@ -70,7 +70,7 @@ describe("EventNdjsonLogger", () => {...@@ -70,7 +70,7 @@ describe("EventNdjsonLogger", () => {
70 '{"type":"turn.completed","threadId":"provider-thread-2","id":"evt-2"}',70 '{"type":"turn.completed","threadId":"provider-thread-2","id":"evt-2"}',
71 );71 );
72 } finally {72 } finally {
73 fs.rmSync(tempDir, { recursive: true, force: true });73 NodeFS.rmSync(tempDir, { recursive: true, force: true });
74 }74 }
75 }),75 }),
76 );76 );
...@@ -79,8 +79,8 @@ describe("EventNdjsonLogger", () => {...@@ -79,8 +79,8 @@ describe("EventNdjsonLogger", () => {
79 "falls back to a global segment when orchestration thread id is missing or invalid",79 "falls back to a global segment when orchestration thread id is missing or invalid",
80 () =>80 () =>
81 Effect.gen(function* () {81 Effect.gen(function* () {
82 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-log-"));82 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-log-"));
83 const basePath = path.join(tempDir, "provider-canonical.ndjson");83 const basePath = NodePath.join(tempDir, "provider-canonical.ndjson");
8484
85 try {85 try {
86 const logger = yield* makeEventNdjsonLogger(basePath, { stream: "orchestration" });86 const logger = yield* makeEventNdjsonLogger(basePath, { stream: "orchestration" });
...@@ -93,10 +93,9 @@ describe("EventNdjsonLogger", () => {...@@ -93,10 +93,9 @@ describe("EventNdjsonLogger", () => {
93 yield* logger.write({ id: "evt-invalid-thread" }, "!!!" as unknown as ThreadId);93 yield* logger.write({ id: "evt-invalid-thread" }, "!!!" as unknown as ThreadId);
94 yield* logger.close();94 yield* logger.close();
9595
96 const globalPath = path.join(tempDir, "_global.log");96 const globalPath = NodePath.join(tempDir, "_global.log");
97 assert.equal(fs.existsSync(globalPath), true);97 assert.equal(NodeFS.existsSync(globalPath), true);
98 const lines = fs98 const lines = NodeFS.readFileSync(globalPath, "utf8")
99 .readFileSync(globalPath, "utf8")
100 .trim()99 .trim()
101 .split("\n")100 .split("\n")
102 .map((line) => parseLogLine(line));101 .map((line) => parseLogLine(line));
...@@ -108,15 +107,15 @@ describe("EventNdjsonLogger", () => {...@@ -108,15 +107,15 @@ describe("EventNdjsonLogger", () => {
108 assert.equal(lines[1]?.stream, "CANON");107 assert.equal(lines[1]?.stream, "CANON");
109 assert.equal(lines[1]?.payload, '{"id":"evt-invalid-thread"}');108 assert.equal(lines[1]?.payload, '{"id":"evt-invalid-thread"}');
110 } finally {109 } finally {
111 fs.rmSync(tempDir, { recursive: true, force: true });110 NodeFS.rmSync(tempDir, { recursive: true, force: true });
112 }111 }
113 }),112 }),
114 );113 );
115114
116 it.effect("serializes concurrent first writes for the same segment", () =>115 it.effect("serializes concurrent first writes for the same segment", () =>
117 Effect.gen(function* () {116 Effect.gen(function* () {
118 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-log-"));117 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-log-"));
119 const basePath = path.join(tempDir, "provider-canonical.ndjson");118 const basePath = NodePath.join(tempDir, "provider-canonical.ndjson");
120119
121 try {120 try {
122 const logger = yield* makeEventNdjsonLogger(basePath, {121 const logger = yield* makeEventNdjsonLogger(basePath, {
...@@ -137,10 +136,9 @@ describe("EventNdjsonLogger", () => {...@@ -137,10 +136,9 @@ describe("EventNdjsonLogger", () => {
137 );136 );
138 yield* logger.close();137 yield* logger.close();
139138
140 const globalPath = path.join(tempDir, "_global.log");139 const globalPath = NodePath.join(tempDir, "_global.log");
141 assert.equal(fs.existsSync(globalPath), true);140 assert.equal(NodeFS.existsSync(globalPath), true);
142 const lines = fs141 const lines = NodeFS.readFileSync(globalPath, "utf8")
143 .readFileSync(globalPath, "utf8")
144 .trim()142 .trim()
145 .split("\n")143 .split("\n")
146 .map((line) => parseLogLine(line));144 .map((line) => parseLogLine(line));
...@@ -151,15 +149,15 @@ describe("EventNdjsonLogger", () => {...@@ -151,15 +149,15 @@ describe("EventNdjsonLogger", () => {
151 '{"id":"evt-concurrent-2"}',149 '{"id":"evt-concurrent-2"}',
152 ]);150 ]);
153 } finally {151 } finally {
154 fs.rmSync(tempDir, { recursive: true, force: true });152 NodeFS.rmSync(tempDir, { recursive: true, force: true });
155 }153 }
156 }),154 }),
157 );155 );
158156
159 it.effect("rotates per-thread files when max size is exceeded", () =>157 it.effect("rotates per-thread files when max size is exceeded", () =>
160 Effect.gen(function* () {158 Effect.gen(function* () {
161 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-log-"));159 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-log-"));
162 const basePath = path.join(tempDir, "provider-native.ndjson");160 const basePath = NodePath.join(tempDir, "provider-native.ndjson");
163161
164 try {162 try {
165 const logger = yield* makeEventNdjsonLogger(basePath, {163 const logger = yield* makeEventNdjsonLogger(basePath, {
...@@ -185,8 +183,7 @@ describe("EventNdjsonLogger", () => {...@@ -185,8 +183,7 @@ describe("EventNdjsonLogger", () => {
185 yield* logger.close();183 yield* logger.close();
186184
187 const fileStem = "thread-rotate.log";185 const fileStem = "thread-rotate.log";
188 const matchingFiles = fs186 const matchingFiles = NodeFS.readdirSync(tempDir)
189 .readdirSync(tempDir)
190 .filter((entry) => entry === fileStem || entry.startsWith(`${fileStem}.`))187 .filter((entry) => entry === fileStem || entry.startsWith(`${fileStem}.`))
191 .toSorted();188 .toSorted();
192189
...@@ -203,7 +200,7 @@ describe("EventNdjsonLogger", () => {...@@ -203,7 +200,7 @@ describe("EventNdjsonLogger", () => {
203 false,200 false,
204 );201 );
205 } finally {202 } finally {
206 fs.rmSync(tempDir, { recursive: true, force: true });203 NodeFS.rmSync(tempDir, { recursive: true, force: true });
207 }204 }
208 }),205 }),
209 );206 );
apps/server/src/provider/Layers/EventNdjsonLogger.ts+4-4
...@@ -6,8 +6,8 @@...@@ -6,8 +6,8 @@
6 * single effect-style text line in a thread-scoped file. Failures are6 * single effect-style text line in a thread-scoped file. Failures are
7 * downgraded to warnings so provider runtime behavior is unaffected.7 * downgraded to warnings so provider runtime behavior is unaffected.
8 */8 */
9import fs from "node:fs";9import * as NodeFS from "node:fs";
10import path from "node:path";10import * as NodePath from "node:path";
1111
12import type { ThreadId } from "@t3tools/contracts";12import type { ThreadId } from "@t3tools/contracts";
13import { RotatingFileSink } from "@t3tools/shared/logging";13import { RotatingFileSink } from "@t3tools/shared/logging";
...@@ -178,7 +178,7 @@ export const makeEventNdjsonLogger = Effect.fn("makeEventNdjsonLogger")(function...@@ -178,7 +178,7 @@ export const makeEventNdjsonLogger = Effect.fn("makeEventNdjsonLogger")(function
178178
179 const directoryReady = yield* Effect.sync(() => {179 const directoryReady = yield* Effect.sync(() => {
180 try {180 try {
181 fs.mkdirSync(path.dirname(filePath), { recursive: true });181 NodeFS.mkdirSync(NodePath.dirname(filePath), { recursive: true });
182 return true;182 return true;
183 } catch (error) {183 } catch (error) {
184 return { ok: false as const, error };184 return { ok: false as const, error };
...@@ -211,7 +211,7 @@ export const makeEventNdjsonLogger = Effect.fn("makeEventNdjsonLogger")(function...@@ -211,7 +211,7 @@ export const makeEventNdjsonLogger = Effect.fn("makeEventNdjsonLogger")(function
211 }211 }
212212
213 return makeThreadWriter({213 return makeThreadWriter({
214 filePath: path.join(path.dirname(filePath), `${threadSegment}.log`),214 filePath: NodePath.join(NodePath.dirname(filePath), `${threadSegment}.log`),
215 maxBytes,215 maxBytes,
216 maxFiles,216 maxFiles,
217 batchWindowMs,217 batchWindowMs,
apps/server/src/provider/Layers/GrokAdapter.test.ts+18-16
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import * as path from "node:path";2import * as NodePath from "node:path";
3import * as os from "node:os";3import * as NodeOS from "node:os";
4import { chmod, mkdtemp, readFile, writeFile } from "node:fs/promises";4import * as NodeFSP from "node:fs/promises";
5import { fileURLToPath } from "node:url";5import * as NodeURL from "node:url";
66
7import * as NodeServices from "@effect/platform-node/NodeServices";7import * as NodeServices from "@effect/platform-node/NodeServices";
8import { assert, it } from "@effect/vitest";8import { assert, it } from "@effect/vitest";
...@@ -26,13 +26,13 @@ import { ServerConfig } from "../../config.ts";...@@ -26,13 +26,13 @@ import { ServerConfig } from "../../config.ts";
26import { makeGrokAdapter } from "./GrokAdapter.ts";26import { makeGrokAdapter } from "./GrokAdapter.ts";
27const decodeGrokSettings = Schema.decodeSync(GrokSettings);27const decodeGrokSettings = Schema.decodeSync(GrokSettings);
2828
29const __dirname = path.dirname(fileURLToPath(import.meta.url));29const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
30const mockAgentPath = path.join(__dirname, "../../../scripts/acp-mock-agent.ts");30const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.ts");
31const mockAgentCommand = process.execPath;31const mockAgentCommand = process.execPath;
3232
33async function makeMockGrokWrapper(extraEnv?: Record<string, string>) {33async function makeMockGrokWrapper(extraEnv?: Record<string, string>) {
34 const dir = await mkdtemp(path.join(os.tmpdir(), "grok-acp-mock-"));34 const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "grok-acp-mock-"));
35 const wrapperPath = path.join(dir, "fake-grok.sh");35 const wrapperPath = NodePath.join(dir, "fake-grok.sh");
36 const envExports = Object.entries(extraEnv ?? {})36 const envExports = Object.entries(extraEnv ?? {})
37 .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)37 .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
38 .join("\n");38 .join("\n");
...@@ -40,8 +40,8 @@ async function makeMockGrokWrapper(extraEnv?: Record<string, string>) {...@@ -40,8 +40,8 @@ async function makeMockGrokWrapper(extraEnv?: Record<string, string>) {
40${envExports}40${envExports}
41exec ${JSON.stringify(mockAgentCommand)} ${JSON.stringify(mockAgentPath)} "$@"41exec ${JSON.stringify(mockAgentCommand)} ${JSON.stringify(mockAgentPath)} "$@"
42`;42`;
43 await writeFile(wrapperPath, script, "utf8");43 await NodeFSP.writeFile(wrapperPath, script, "utf8");
44 await chmod(wrapperPath, 0o755);44 await NodeFSP.chmod(wrapperPath, 0o755);
45 return wrapperPath;45 return wrapperPath;
46}46}
4747
...@@ -51,7 +51,7 @@ function waitForFileContent(filePath: string, attempts = 40): Effect.Effect<stri...@@ -51,7 +51,7 @@ function waitForFileContent(filePath: string, attempts = 40): Effect.Effect<stri
51 if (remainingAttempts <= 0) {51 if (remainingAttempts <= 0) {
52 return yield* Effect.die(new Error(`Timed out waiting for file content at ${filePath}`));52 return yield* Effect.die(new Error(`Timed out waiting for file content at ${filePath}`));
53 }53 }
54 const raw = yield* Effect.tryPromise(() => readFile(filePath, "utf8")).pipe(54 const raw = yield* Effect.tryPromise(() => NodeFSP.readFile(filePath, "utf8")).pipe(
55 Effect.orElseSucceed(() => ""),55 Effect.orElseSucceed(() => ""),
56 );56 );
57 if (raw.trim().length > 0) {57 if (raw.trim().length > 0) {
...@@ -64,7 +64,7 @@ function waitForFileContent(filePath: string, attempts = 40): Effect.Effect<stri...@@ -64,7 +64,7 @@ function waitForFileContent(filePath: string, attempts = 40): Effect.Effect<stri
64}64}
6565
66async function readJsonLines(filePath: string) {66async function readJsonLines(filePath: string) {
67 const raw = await readFile(filePath, "utf8");67 const raw = await NodeFSP.readFile(filePath, "utf8");
68 return raw68 return raw
69 .split("\n")69 .split("\n")
70 .map((line) => line.trim())70 .map((line) => line.trim())
...@@ -149,9 +149,9 @@ it.layer(grokAdapterTestLayer)("GrokAdapterLive", (it) => {...@@ -149,9 +149,9 @@ it.layer(grokAdapterTestLayer)("GrokAdapterLive", (it) => {
149 Effect.gen(function* () {149 Effect.gen(function* () {
150 const threadId = ThreadId.make("grok-stop-session-close");150 const threadId = ThreadId.make("grok-stop-session-close");
151 const tempDir = yield* Effect.promise(() =>151 const tempDir = yield* Effect.promise(() =>
152 mkdtemp(path.join(os.tmpdir(), "grok-adapter-exit-log-")),152 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "grok-adapter-exit-log-")),
153 );153 );
154 const exitLogPath = path.join(tempDir, "exit.log");154 const exitLogPath = NodePath.join(tempDir, "exit.log");
155155
156 const wrapperPath = yield* Effect.promise(() =>156 const wrapperPath = yield* Effect.promise(() =>
157 makeMockGrokWrapper({157 makeMockGrokWrapper({
...@@ -227,8 +227,10 @@ it.layer(grokAdapterTestLayer)("GrokAdapterLive", (it) => {...@@ -227,8 +227,10 @@ it.layer(grokAdapterTestLayer)("GrokAdapterLive", (it) => {
227 it.effect("responds to ACP approvals using provider-supplied option ids", () =>227 it.effect("responds to ACP approvals using provider-supplied option ids", () =>
228 Effect.gen(function* () {228 Effect.gen(function* () {
229 const threadId = ThreadId.make("grok-custom-approval-option-id");229 const threadId = ThreadId.make("grok-custom-approval-option-id");
230 const tempDir = yield* Effect.promise(() => mkdtemp(path.join(os.tmpdir(), "grok-acp-")));230 const tempDir = yield* Effect.promise(() =>
231 const requestLogPath = path.join(tempDir, "requests.ndjson");231 NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "grok-acp-")),
232 );
233 const requestLogPath = NodePath.join(tempDir, "requests.ndjson");
232 const wrapperPath = yield* Effect.promise(() =>234 const wrapperPath = yield* Effect.promise(() =>
233 makeMockGrokWrapper({235 makeMockGrokWrapper({
234 T3_ACP_REQUEST_LOG_PATH: requestLogPath,236 T3_ACP_REQUEST_LOG_PATH: requestLogPath,
apps/server/src/provider/Layers/OpenCodeAdapter.test.ts+48-48
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import assert from "node:assert/strict";1import * as NodeAssert from "node:assert/strict";
2import * as NodeServices from "@effect/platform-node/NodeServices";2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { it } from "@effect/vitest";3import { it } from "@effect/vitest";
4import * as Context from "effect/Context";4import * as Context from "effect/Context";
...@@ -238,11 +238,11 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -238,11 +238,11 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
238 runtimeMode: "full-access",238 runtimeMode: "full-access",
239 });239 });
240240
241 assert.equal(session.provider, "opencode");241 NodeAssert.equal(session.provider, "opencode");
242 assert.equal(session.threadId, "thread-opencode");242 NodeAssert.equal(session.threadId, "thread-opencode");
243 assert.deepEqual(runtimeMock.state.startCalls, []);243 NodeAssert.deepEqual(runtimeMock.state.startCalls, []);
244 assert.deepEqual(runtimeMock.state.sessionCreateUrls, ["http://127.0.0.1:9999"]);244 NodeAssert.deepEqual(runtimeMock.state.sessionCreateUrls, ["http://127.0.0.1:9999"]);
245 assert.deepEqual(runtimeMock.state.authHeaders, [245 NodeAssert.deepEqual(runtimeMock.state.authHeaders, [
246 `Basic ${btoa("opencode:secret-password")}`,246 `Basic ${btoa("opencode:secret-password")}`,
247 ]);247 ]);
248 }),248 }),
...@@ -259,8 +259,8 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -259,8 +259,8 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
259259
260 yield* adapter.stopSession(asThreadId("thread-opencode"));260 yield* adapter.stopSession(asThreadId("thread-opencode"));
261261
262 assert.deepEqual(runtimeMock.state.startCalls, []);262 NodeAssert.deepEqual(runtimeMock.state.startCalls, []);
263 assert.deepEqual(263 NodeAssert.deepEqual(
264 runtimeMock.state.abortCalls.includes("http://127.0.0.1:9999/session"),264 runtimeMock.state.abortCalls.includes("http://127.0.0.1:9999/session"),
265 true,265 true,
266 );266 );
...@@ -286,7 +286,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -286,7 +286,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
286 yield* adapter.stopSession(threadId);286 yield* adapter.stopSession(threadId);
287287
288 const events = Array.from(yield* Fiber.join(eventsFiber).pipe(Effect.timeout("1 second")));288 const events = Array.from(yield* Fiber.join(eventsFiber).pipe(Effect.timeout("1 second")));
289 assert.deepEqual(289 NodeAssert.deepEqual(
290 events.map((event) => event.type),290 events.map((event) => event.type),
291 ["session.started", "thread.started", "session.exited"],291 ["session.started", "thread.started", "session.exited"],
292 );292 );
...@@ -316,11 +316,11 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -316,11 +316,11 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
316 yield* Effect.exit(adapter.stopAll());316 yield* Effect.exit(adapter.stopAll());
317 const sessions = yield* adapter.listSessions();317 const sessions = yield* adapter.listSessions();
318318
319 assert.deepEqual(runtimeMock.state.closeCalls, [319 NodeAssert.deepEqual(runtimeMock.state.closeCalls, [
320 "http://127.0.0.1:9999",320 "http://127.0.0.1:9999",
321 "http://127.0.0.1:9999",321 "http://127.0.0.1:9999",
322 ]);322 ]);
323 assert.deepEqual(sessions, []);323 NodeAssert.deepEqual(sessions, []);
324 }),324 }),
325 );325 );
326326
...@@ -348,7 +348,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -348,7 +348,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
348 scopeClosed = true;348 scopeClosed = true;
349349
350 const exit = yield* Fiber.await(eventsFiber).pipe(Effect.timeout("1 second"));350 const exit = yield* Fiber.await(eventsFiber).pipe(Effect.timeout("1 second"));
351 assert.equal(Exit.hasInterrupts(exit), true);351 NodeAssert.equal(Exit.hasInterrupts(exit), true);
352 } finally {352 } finally {
353 if (!scopeClosed) {353 if (!scopeClosed) {
354 yield* Scope.close(scope, Exit.void).pipe(Effect.ignore);354 yield* Scope.close(scope, Exit.void).pipe(Effect.ignore);
...@@ -379,19 +379,19 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -379,19 +379,19 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
379 .pipe(Effect.flip);379 .pipe(Effect.flip);
380 const sessions = yield* adapter.listSessions();380 const sessions = yield* adapter.listSessions();
381381
382 assert.equal(error._tag, "ProviderAdapterRequestError");382 NodeAssert.equal(error._tag, "ProviderAdapterRequestError");
383 if (error._tag !== "ProviderAdapterRequestError") {383 if (error._tag !== "ProviderAdapterRequestError") {
384 throw new Error("Unexpected error type");384 throw new Error("Unexpected error type");
385 }385 }
386 assert.equal(error.detail, "prompt failed");386 NodeAssert.equal(error.detail, "prompt failed");
387 assert.equal(387 NodeAssert.equal(
388 error.message,388 error.message,
389 "Provider adapter request failed (opencode) for session.promptAsync: prompt failed",389 "Provider adapter request failed (opencode) for session.promptAsync: prompt failed",
390 );390 );
391 assert.equal(sessions.length, 1);391 NodeAssert.equal(sessions.length, 1);
392 assert.equal(sessions[0]?.status, "ready");392 NodeAssert.equal(sessions[0]?.status, "ready");
393 assert.equal(sessions[0]?.activeTurnId, undefined);393 NodeAssert.equal(sessions[0]?.activeTurnId, undefined);
394 assert.equal(sessions[0]?.lastError, "prompt failed");394 NodeAssert.equal(sessions[0]?.lastError, "prompt failed");
395 }),395 }),
396 );396 );
397397
...@@ -424,13 +424,13 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -424,13 +424,13 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
424 model: "openai/gpt-5",424 model: "openai/gpt-5",
425 },425 },
426 });426 });
427 assert.equal(String(steeredTurn.turnId), String(turn.turnId));427 NodeAssert.equal(String(steeredTurn.turnId), String(turn.turnId));
428428
429 const sessions = yield* adapter.listSessions();429 const sessions = yield* adapter.listSessions();
430 const session = sessions.find((entry) => entry.threadId === threadId);430 const session = sessions.find((entry) => entry.threadId === threadId);
431 assert.equal(session?.status, "running");431 NodeAssert.equal(session?.status, "running");
432 assert.equal(String(session?.activeTurnId), String(turn.turnId));432 NodeAssert.equal(String(session?.activeTurnId), String(turn.turnId));
433 assert.equal(runtimeMock.state.promptCalls.length, 2);433 NodeAssert.equal(runtimeMock.state.promptCalls.length, 2);
434 }),434 }),
435 );435 );
436436
...@@ -466,11 +466,11 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -466,11 +466,11 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
466 .pipe(Effect.flip);466 .pipe(Effect.flip);
467467
468 // The original turn keeps running — only the steer prompt failed.468 // The original turn keeps running — only the steer prompt failed.
469 assert.equal(error._tag, "ProviderAdapterRequestError");469 NodeAssert.equal(error._tag, "ProviderAdapterRequestError");
470 const sessions = yield* adapter.listSessions();470 const sessions = yield* adapter.listSessions();
471 const session = sessions.find((entry) => entry.threadId === threadId);471 const session = sessions.find((entry) => entry.threadId === threadId);
472 assert.equal(session?.status, "running");472 NodeAssert.equal(session?.status, "running");
473 assert.equal(String(session?.activeTurnId), String(turn.turnId));473 NodeAssert.equal(String(session?.activeTurnId), String(turn.turnId));
474 }),474 }),
475 );475 );
476476
...@@ -508,7 +508,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -508,7 +508,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
508 ),508 ),
509 });509 });
510510
511 assert.deepEqual(runtimeMock.state.promptCalls.at(-1), {511 NodeAssert.deepEqual(runtimeMock.state.promptCalls.at(-1), {
512 sessionID: "http://127.0.0.1:9999/session",512 sessionID: "http://127.0.0.1:9999/session",
513 model: {513 model: {
514 providerID: "anthropic",514 providerID: "anthropic",
...@@ -552,7 +552,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -552,7 +552,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
552 input: "Fix it",552 input: "Fix it",
553 });553 });
554554
555 assert.deepEqual(runtimeMock.state.promptCalls.at(-1), {555 NodeAssert.deepEqual(runtimeMock.state.promptCalls.at(-1), {
556 sessionID: "http://127.0.0.1:9999/session",556 sessionID: "http://127.0.0.1:9999/session",
557 model: {557 model: {
558 providerID: "anthropic",558 providerID: "anthropic",
...@@ -596,15 +596,15 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -596,15 +596,15 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
596 })596 })
597 .pipe(Effect.flip);597 .pipe(Effect.flip);
598598
599 assert.equal(error._tag, "ProviderAdapterValidationError");599 NodeAssert.equal(error._tag, "ProviderAdapterValidationError");
600 if (error._tag !== "ProviderAdapterValidationError") {600 if (error._tag !== "ProviderAdapterValidationError") {
601 throw new Error("Unexpected error type");601 throw new Error("Unexpected error type");
602 }602 }
603 assert.equal(603 NodeAssert.equal(
604 error.issue,604 error.issue,
605 "OpenCode model selection is bound to instance 'opencode', expected 'opencode_zen'.",605 "OpenCode model selection is bound to instance 'opencode', expected 'opencode_zen'.",
606 );606 );
607 assert.deepEqual(runtimeMock.state.promptCalls, []);607 NodeAssert.deepEqual(runtimeMock.state.promptCalls, []);
608 }).pipe(Effect.provide(adapterLayer));608 }).pipe(Effect.provide(adapterLayer));
609 });609 });
610610
...@@ -631,10 +631,10 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -631,10 +631,10 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
631631
632 const snapshot = yield* adapter.rollbackThread(threadId, 2);632 const snapshot = yield* adapter.rollbackThread(threadId, 2);
633633
634 assert.deepEqual(runtimeMock.state.revertCalls, [634 NodeAssert.deepEqual(runtimeMock.state.revertCalls, [
635 { sessionID: "http://127.0.0.1:9999/session" },635 { sessionID: "http://127.0.0.1:9999/session" },
636 ]);636 ]);
637 assert.deepEqual(snapshot.turns, []);637 NodeAssert.deepEqual(snapshot.turns, []);
638 }),638 }),
639 );639 );
640640
...@@ -644,11 +644,11 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -644,11 +644,11 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
644 const overlapDelta = appendOpenCodeAssistantTextDelta(firstUpdate.latestText, "lo world");644 const overlapDelta = appendOpenCodeAssistantTextDelta(firstUpdate.latestText, "lo world");
645 const secondUpdate = mergeOpenCodeAssistantText(overlapDelta.nextText, "Hellolo world");645 const secondUpdate = mergeOpenCodeAssistantText(overlapDelta.nextText, "Hellolo world");
646646
647 assert.deepEqual(647 NodeAssert.deepEqual(
648 [firstUpdate.deltaToEmit, overlapDelta.deltaToEmit, secondUpdate.deltaToEmit],648 [firstUpdate.deltaToEmit, overlapDelta.deltaToEmit, secondUpdate.deltaToEmit],
649 ["Hello", "lo world", ""],649 ["Hello", "lo world", ""],
650 );650 );
651 assert.equal(secondUpdate.latestText, "Hellolo world");651 NodeAssert.equal(secondUpdate.latestText, "Hellolo world");
652 }),652 }),
653 );653 );
654654
...@@ -721,14 +721,14 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -721,14 +721,14 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
721721
722 const events = Array.from(yield* Fiber.join(eventsFiber).pipe(Effect.timeout("1 second")));722 const events = Array.from(yield* Fiber.join(eventsFiber).pipe(Effect.timeout("1 second")));
723 const deltas = events.filter((event) => event.type === "content.delta");723 const deltas = events.filter((event) => event.type === "content.delta");
724 assert.deepEqual(724 NodeAssert.deepEqual(
725 deltas.map((event) => (event.type === "content.delta" ? event.payload.delta : "")),725 deltas.map((event) => (event.type === "content.delta" ? event.payload.delta : "")),
726 ["A B", "Bonus"],726 ["A B", "Bonus"],
727 );727 );
728 assert.equal(events.at(-1)?.type, "item.completed");728 NodeAssert.equal(events.at(-1)?.type, "item.completed");
729 const completed = events.at(-1);729 const completed = events.at(-1);
730 if (completed?.type === "item.completed") {730 if (completed?.type === "item.completed") {
731 assert.equal(completed.payload.detail, "A BBonus");731 NodeAssert.equal(completed.payload.detail, "A BBonus");
732 }732 }
733 }),733 }),
734 );734 );
...@@ -820,27 +820,27 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -820,27 +820,27 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
820 return started;820 return started;
821 }).pipe(Effect.provide(adapterLayer));821 }).pipe(Effect.provide(adapterLayer));
822822
823 assert.equal(session.threadId, "thread-native-log");823 NodeAssert.equal(session.threadId, "thread-native-log");
824 assert.equal(nativeEvents.length, 1);824 NodeAssert.equal(nativeEvents.length, 1);
825 assert.equal(825 NodeAssert.equal(
826 nativeEvents.some((record) => record.event?.provider === "opencode"),826 nativeEvents.some((record) => record.event?.provider === "opencode"),
827 true,827 true,
828 );828 );
829 assert.equal(829 NodeAssert.equal(
830 nativeEvents.some(830 nativeEvents.some(
831 (record) => record.event?.providerThreadId === "http://127.0.0.1:9999/session",831 (record) => record.event?.providerThreadId === "http://127.0.0.1:9999/session",
832 ),832 ),
833 true,833 true,
834 );834 );
835 assert.equal(835 NodeAssert.equal(
836 nativeEvents.some((record) => record.event?.threadId === "thread-native-log"),836 nativeEvents.some((record) => record.event?.threadId === "thread-native-log"),
837 true,837 true,
838 );838 );
839 assert.equal(839 NodeAssert.equal(
840 nativeEvents.some((record) => record.event?.type === "message.updated"),840 nativeEvents.some((record) => record.event?.type === "message.updated"),
841 true,841 true,
842 );842 );
843 assert.equal(843 NodeAssert.equal(
844 nativeThreadIds.every((threadId) => threadId === "thread-native-log"),844 nativeThreadIds.every((threadId) => threadId === "thread-native-log"),
845 true,845 true,
846 );846 );
...@@ -911,9 +911,9 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {...@@ -911,9 +911,9 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => {
911 };911 };
912 }).pipe(Effect.provide(adapterLayer));912 }).pipe(Effect.provide(adapterLayer));
913913
914 assert.equal(sessions.length, 1);914 NodeAssert.equal(sessions.length, 1);
915 assert.equal(sessions[0]?.threadId, "thread-native-log-failure");915 NodeAssert.equal(sessions[0]?.threadId, "thread-native-log-failure");
916 assert.deepEqual(closeCallsDuringRun, []);916 NodeAssert.deepEqual(closeCallsDuringRun, []);
917 }),917 }),
918 );918 );
919});919});
apps/server/src/provider/Layers/OpenCodeProvider.test.ts+22-19
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import assert from "node:assert/strict";1import * as NodeAssert from "node:assert/strict";
22
3import * as NodeServices from "@effect/platform-node/NodeServices";3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { it } from "@effect/vitest";4import { it } from "@effect/vitest";
...@@ -122,9 +122,12 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {...@@ -122,9 +122,12 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {
122 runtimeMock.state.runVersionError = new Error("spawn opencode ENOENT");122 runtimeMock.state.runVersionError = new Error("spawn opencode ENOENT");
123 const snapshot = yield* checkOpenCodeProviderStatus(makeOpenCodeSettings(), process.cwd());123 const snapshot = yield* checkOpenCodeProviderStatus(makeOpenCodeSettings(), process.cwd());
124124
125 assert.equal(snapshot.status, "error");125 NodeAssert.equal(snapshot.status, "error");
126 assert.equal(snapshot.installed, false);126 NodeAssert.equal(snapshot.installed, false);
127 assert.equal(snapshot.message, "OpenCode CLI (`opencode`) is not installed or not on PATH.");127 NodeAssert.equal(
128 snapshot.message,
129 "OpenCode CLI (`opencode`) is not installed or not on PATH.",
130 );
128 }),131 }),
129 );132 );
130133
...@@ -133,9 +136,9 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {...@@ -133,9 +136,9 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {
133 runtimeMock.state.runVersionError = new Error("An error occurred in Effect.tryPromise");136 runtimeMock.state.runVersionError = new Error("An error occurred in Effect.tryPromise");
134 const snapshot = yield* checkOpenCodeProviderStatus(makeOpenCodeSettings(), process.cwd());137 const snapshot = yield* checkOpenCodeProviderStatus(makeOpenCodeSettings(), process.cwd());
135138
136 assert.equal(snapshot.status, "error");139 NodeAssert.equal(snapshot.status, "error");
137 assert.equal(snapshot.installed, true);140 NodeAssert.equal(snapshot.installed, true);
138 assert.equal(snapshot.message, "Failed to execute OpenCode CLI health check.");141 NodeAssert.equal(snapshot.message, "Failed to execute OpenCode CLI health check.");
139 }),142 }),
140 );143 );
141144
...@@ -174,20 +177,20 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {...@@ -174,20 +177,20 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {
174 const snapshot = yield* checkOpenCodeProviderStatus(makeOpenCodeSettings(), process.cwd());177 const snapshot = yield* checkOpenCodeProviderStatus(makeOpenCodeSettings(), process.cwd());
175 const model = snapshot.models.find((entry) => entry.slug === "openai/gpt-5.4");178 const model = snapshot.models.find((entry) => entry.slug === "openai/gpt-5.4");
176179
177 assert.ok(model);180 NodeAssert.ok(model);
178 const variantDescriptor = model.capabilities?.optionDescriptors?.find(181 const variantDescriptor = model.capabilities?.optionDescriptors?.find(
179 (descriptor) => descriptor.id === "variant" && descriptor.type === "select",182 (descriptor) => descriptor.id === "variant" && descriptor.type === "select",
180 );183 );
181 assert.ok(variantDescriptor && variantDescriptor.type === "select");184 NodeAssert.ok(variantDescriptor && variantDescriptor.type === "select");
182 assert.equal(185 NodeAssert.equal(
183 variantDescriptor.options.find((option) => option.isDefault === true)?.id,186 variantDescriptor.options.find((option) => option.isDefault === true)?.id,
184 "medium",187 "medium",
185 );188 );
186 const agentDescriptor = model.capabilities?.optionDescriptors?.find(189 const agentDescriptor = model.capabilities?.optionDescriptors?.find(
187 (descriptor) => descriptor.id === "agent" && descriptor.type === "select",190 (descriptor) => descriptor.id === "agent" && descriptor.type === "select",
188 );191 );
189 assert.ok(agentDescriptor && agentDescriptor.type === "select");192 NodeAssert.ok(agentDescriptor && agentDescriptor.type === "select");
190 assert.equal(193 NodeAssert.equal(
191 agentDescriptor.options.find((option) => option.isDefault === true)?.id,194 agentDescriptor.options.find((option) => option.isDefault === true)?.id,
192 "build",195 "build",
193 );196 );
...@@ -198,7 +201,7 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {...@@ -198,7 +201,7 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {
198 Effect.gen(function* () {201 Effect.gen(function* () {
199 yield* checkOpenCodeProviderStatus(makeOpenCodeSettings(), process.cwd());202 yield* checkOpenCodeProviderStatus(makeOpenCodeSettings(), process.cwd());
200203
201 assert.equal(runtimeMock.state.closeCalls, 1);204 NodeAssert.equal(runtimeMock.state.closeCalls, 1);
202 }),205 }),
203 );206 );
204});207});
...@@ -215,9 +218,9 @@ it.layer(testLayer)("checkOpenCodeProviderStatus with configured server URL", (i...@@ -215,9 +218,9 @@ it.layer(testLayer)("checkOpenCodeProviderStatus with configured server URL", (i
215 process.cwd(),218 process.cwd(),
216 );219 );
217220
218 assert.equal(snapshot.status, "error");221 NodeAssert.equal(snapshot.status, "error");
219 assert.equal(snapshot.installed, true);222 NodeAssert.equal(snapshot.installed, true);
220 assert.equal(223 NodeAssert.equal(
221 snapshot.message,224 snapshot.message,
222 "OpenCode server rejected authentication. Check the server URL and password.",225 "OpenCode server rejected authentication. Check the server URL and password.",
223 );226 );
...@@ -237,9 +240,9 @@ it.layer(testLayer)("checkOpenCodeProviderStatus with configured server URL", (i...@@ -237,9 +240,9 @@ it.layer(testLayer)("checkOpenCodeProviderStatus with configured server URL", (i
237 process.cwd(),240 process.cwd(),
238 );241 );
239242
240 assert.equal(snapshot.status, "error");243 NodeAssert.equal(snapshot.status, "error");
241 assert.equal(snapshot.installed, true);244 NodeAssert.equal(snapshot.installed, true);
242 assert.equal(245 NodeAssert.equal(
243 snapshot.message,246 snapshot.message,
244 "Couldn't reach the configured OpenCode server at http://127.0.0.1:9999. Check that the server is running and the URL is correct.",247 "Couldn't reach the configured OpenCode server at http://127.0.0.1:9999. Check that the server is running and the URL is correct.",
245 );248 );
apps/server/src/provider/Layers/ProviderService.test.ts+21-15
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import os from "node:os";3import * as NodeOS from "node:os";
4import path from "node:path";4import * as NodePath from "node:path";
55
6import type {6import type {
7 ProviderApprovalDecision,7 ProviderApprovalDecision,
...@@ -644,8 +644,8 @@ it.effect("ProviderServiceLive writes canonical events to the emitting thread se...@@ -644,8 +644,8 @@ it.effect("ProviderServiceLive writes canonical events to the emitting thread se
644644
645it.effect("ProviderServiceLive keeps persisted resumable sessions on startup", () =>645it.effect("ProviderServiceLive keeps persisted resumable sessions on startup", () =>
646 Effect.gen(function* () {646 Effect.gen(function* () {
647 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-service-"));647 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-service-"));
648 const dbPath = path.join(tempDir, "orchestration.sqlite");648 const dbPath = NodePath.join(tempDir, "orchestration.sqlite");
649649
650 const codex = makeFakeCodexAdapter();650 const codex = makeFakeCodexAdapter();
651 const registry = makeAdapterRegistryMock({651 const registry = makeAdapterRegistryMock({
...@@ -706,7 +706,7 @@ it.effect("ProviderServiceLive keeps persisted resumable sessions on startup", (...@@ -706,7 +706,7 @@ it.effect("ProviderServiceLive keeps persisted resumable sessions on startup", (
706 }).pipe(Effect.provide(persistenceLayer));706 }).pipe(Effect.provide(persistenceLayer));
707 assert.equal(legacyTableRows.length, 0);707 assert.equal(legacyTableRows.length, 0);
708708
709 fs.rmSync(tempDir, { recursive: true, force: true });709 NodeFS.rmSync(tempDir, { recursive: true, force: true });
710 }).pipe(Effect.provide(NodeServices.layer)),710 }).pipe(Effect.provide(NodeServices.layer)),
711);711);
712712
...@@ -714,8 +714,10 @@ it.effect(...@@ -714,8 +714,10 @@ it.effect(
714 "ProviderServiceLive restores rollback routing after restart using persisted thread mapping",714 "ProviderServiceLive restores rollback routing after restart using persisted thread mapping",
715 () =>715 () =>
716 Effect.gen(function* () {716 Effect.gen(function* () {
717 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-service-restart-"));717 const tempDir = NodeFS.mkdtempSync(
718 const dbPath = path.join(tempDir, "orchestration.sqlite");718 NodePath.join(NodeOS.tmpdir(), "t3-provider-service-restart-"),
719 );
720 const dbPath = NodePath.join(tempDir, "orchestration.sqlite");
719 const persistenceLayer = makeSqlitePersistenceLive(dbPath);721 const persistenceLayer = makeSqlitePersistenceLive(dbPath);
720 const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe(722 const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe(
721 Layer.provide(persistenceLayer),723 Layer.provide(persistenceLayer),
...@@ -834,7 +836,7 @@ it.effect(...@@ -834,7 +836,7 @@ it.effect(
834 assert.equal(typeof rollbackCall?.[0], "string");836 assert.equal(typeof rollbackCall?.[0], "string");
835 assert.equal(rollbackCall?.[1], 1);837 assert.equal(rollbackCall?.[1], 1);
836838
837 fs.rmSync(tempDir, { recursive: true, force: true });839 NodeFS.rmSync(tempDir, { recursive: true, force: true });
838 }).pipe(Effect.provide(NodeServices.layer)),840 }).pipe(Effect.provide(NodeServices.layer)),
839);841);
840842
...@@ -1283,8 +1285,10 @@ routing.layer("ProviderServiceLive routing", (it) => {...@@ -1283,8 +1285,10 @@ routing.layer("ProviderServiceLive routing", (it) => {
12831285
1284 it.effect("reuses persisted resume cursor when startSession is called after a restart", () =>1286 it.effect("reuses persisted resume cursor when startSession is called after a restart", () =>
1285 Effect.gen(function* () {1287 Effect.gen(function* () {
1286 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-service-start-"));1288 const tempDir = NodeFS.mkdtempSync(
1287 const dbPath = path.join(tempDir, "orchestration.sqlite");1289 NodePath.join(NodeOS.tmpdir(), "t3-provider-service-start-"),
1290 );
1291 const dbPath = NodePath.join(tempDir, "orchestration.sqlite");
1288 const persistenceLayer = makeSqlitePersistenceLive(dbPath);1292 const persistenceLayer = makeSqlitePersistenceLive(dbPath);
1289 const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe(1293 const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe(
1290 Layer.provide(persistenceLayer),1294 Layer.provide(persistenceLayer),
...@@ -1379,7 +1383,7 @@ routing.layer("ProviderServiceLive routing", (it) => {...@@ -1379,7 +1383,7 @@ routing.layer("ProviderServiceLive routing", (it) => {
1379 assert.equal(startPayload.threadId, initial.threadId);1383 assert.equal(startPayload.threadId, initial.threadId);
1380 }1384 }
13811385
1382 fs.rmSync(tempDir, { recursive: true, force: true });1386 NodeFS.rmSync(tempDir, { recursive: true, force: true });
1383 }).pipe(Effect.provide(NodeServices.layer)),1387 }).pipe(Effect.provide(NodeServices.layer)),
1384 );1388 );
13851389
...@@ -1387,8 +1391,10 @@ routing.layer("ProviderServiceLive routing", (it) => {...@@ -1387,8 +1391,10 @@ routing.layer("ProviderServiceLive routing", (it) => {
1387 "reuses persisted cwd when startSession resumes a claude session without cwd input",1391 "reuses persisted cwd when startSession resumes a claude session without cwd input",
1388 () =>1392 () =>
1389 Effect.gen(function* () {1393 Effect.gen(function* () {
1390 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-service-cwd-"));1394 const tempDir = NodeFS.mkdtempSync(
1391 const dbPath = path.join(tempDir, "orchestration.sqlite");1395 NodePath.join(NodeOS.tmpdir(), "t3-provider-service-cwd-"),
1396 );
1397 const dbPath = NodePath.join(tempDir, "orchestration.sqlite");
1392 const persistenceLayer = makeSqlitePersistenceLive(dbPath);1398 const persistenceLayer = makeSqlitePersistenceLive(dbPath);
1393 const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe(1399 const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe(
1394 Layer.provide(persistenceLayer),1400 Layer.provide(persistenceLayer),
...@@ -1477,7 +1483,7 @@ routing.layer("ProviderServiceLive routing", (it) => {...@@ -1477,7 +1483,7 @@ routing.layer("ProviderServiceLive routing", (it) => {
1477 assert.equal(startPayload.threadId, initial.threadId);1483 assert.equal(startPayload.threadId, initial.threadId);
1478 }1484 }
14791485
1480 fs.rmSync(tempDir, { recursive: true, force: true });1486 NodeFS.rmSync(tempDir, { recursive: true, force: true });
1481 }).pipe(Effect.provide(NodeServices.layer)),1487 }).pipe(Effect.provide(NodeServices.layer)),
1482 );1488 );
1483});1489});
apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts+6-6
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import os from "node:os";3import * as NodeOS from "node:os";
4import path from "node:path";4import * as NodePath from "node:path";
55
6import * as NodeServices from "@effect/platform-node/NodeServices";6import * as NodeServices from "@effect/platform-node/NodeServices";
7import { ProviderDriverKind, ThreadId } from "@t3tools/contracts";7import { ProviderDriverKind, ThreadId } from "@t3tools/contracts";
...@@ -229,8 +229,8 @@ it.layer(makeDirectoryLayer(SqlitePersistenceMemory))("ProviderSessionDirectoryL...@@ -229,8 +229,8 @@ it.layer(makeDirectoryLayer(SqlitePersistenceMemory))("ProviderSessionDirectoryL
229229
230 it("rehydrates persisted mappings across layer restart", () =>230 it("rehydrates persisted mappings across layer restart", () =>
231 Effect.gen(function* () {231 Effect.gen(function* () {
232 const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-directory-"));232 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-directory-"));
233 const dbPath = path.join(tempDir, "orchestration.sqlite");233 const dbPath = NodePath.join(tempDir, "orchestration.sqlite");
234 const directoryLayer = makeDirectoryLayer(makeSqlitePersistenceLive(dbPath));234 const directoryLayer = makeDirectoryLayer(makeSqlitePersistenceLive(dbPath));
235235
236 const threadId = ThreadId.make("thread-restart");236 const threadId = ThreadId.make("thread-restart");
...@@ -266,6 +266,6 @@ it.layer(makeDirectoryLayer(SqlitePersistenceMemory))("ProviderSessionDirectoryL...@@ -266,6 +266,6 @@ it.layer(makeDirectoryLayer(SqlitePersistenceMemory))("ProviderSessionDirectoryL
266 assert.equal(legacyTableRows.length, 0);266 assert.equal(legacyTableRows.length, 0);
267 }).pipe(Effect.provide(directoryLayer));267 }).pipe(Effect.provide(directoryLayer));
268268
269 fs.rmSync(tempDir, { recursive: true, force: true });269 NodeFS.rmSync(tempDir, { recursive: true, force: true });
270 }));270 }));
271});271});
apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts+10-10
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import * as path from "node:path";2import * as NodePath from "node:path";
3import * as os from "node:os";3import * as NodeOS from "node:os";
4import { fileURLToPath } from "node:url";4import * as NodeURL from "node:url";
5import { mkdtempSync, readFileSync, rmSync } from "node:fs";5import * as NodeFS from "node:fs";
66
7import * as NodeServices from "@effect/platform-node/NodeServices";7import * as NodeServices from "@effect/platform-node/NodeServices";
8import { it } from "@effect/vitest";8import { it } from "@effect/vitest";
...@@ -13,8 +13,8 @@ import { describe, expect } from "vite-plus/test";...@@ -13,8 +13,8 @@ import { describe, expect } from "vite-plus/test";
13import * as AcpSessionRuntime from "./AcpSessionRuntime.ts";13import * as AcpSessionRuntime from "./AcpSessionRuntime.ts";
14import type * as EffectAcpProtocol from "effect-acp/protocol";14import type * as EffectAcpProtocol from "effect-acp/protocol";
1515
16const __dirname = path.dirname(fileURLToPath(import.meta.url));16const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
17const mockAgentPath = path.join(__dirname, "../../../scripts/acp-mock-agent.ts");17const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.ts");
18const mockAgentCommand = "node";18const mockAgentCommand = "node";
19const mockAgentArgs = [mockAgentPath];19const mockAgentArgs = [mockAgentPath];
2020
...@@ -347,8 +347,8 @@ describe("AcpSessionRuntime", () => {...@@ -347,8 +347,8 @@ describe("AcpSessionRuntime", () => {
347 });347 });
348348
349 it.effect("rejects invalid config option values before sending session/set_config_option", () => {349 it.effect("rejects invalid config option values before sending session/set_config_option", () => {
350 const tempDir = mkdtempSync(path.join(os.tmpdir(), "acp-runtime-"));350 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "acp-runtime-"));
351 const requestLogPath = path.join(tempDir, "requests.ndjson");351 const requestLogPath = NodePath.join(tempDir, "requests.ndjson");
352 return Effect.gen(function* () {352 return Effect.gen(function* () {
353 const runtime = yield* AcpSessionRuntime.AcpSessionRuntime;353 const runtime = yield* AcpSessionRuntime.AcpSessionRuntime;
354 yield* runtime.start();354 yield* runtime.start();
...@@ -363,7 +363,7 @@ describe("AcpSessionRuntime", () => {...@@ -363,7 +363,7 @@ describe("AcpSessionRuntime", () => {
363 expect(error.message).toContain("composer-2[fast=true]");363 expect(error.message).toContain("composer-2[fast=true]");
364 }364 }
365365
366 const recordedRequests = readFileSync(requestLogPath, "utf8")366 const recordedRequests = NodeFS.readFileSync(requestLogPath, "utf8")
367 .trim()367 .trim()
368 .split("\n")368 .split("\n")
369 .filter((line) => line.length > 0)369 .filter((line) => line.length > 0)
...@@ -392,7 +392,7 @@ describe("AcpSessionRuntime", () => {...@@ -392,7 +392,7 @@ describe("AcpSessionRuntime", () => {
392 ),392 ),
393 Effect.scoped,393 Effect.scoped,
394 Effect.provide(NodeServices.layer),394 Effect.provide(NodeServices.layer),
395 Effect.ensuring(Effect.sync(() => rmSync(tempDir, { recursive: true, force: true }))),395 Effect.ensuring(Effect.sync(() => NodeFS.rmSync(tempDir, { recursive: true, force: true }))),
396 );396 );
397 });397 });
398});398});
apps/server/src/provider/opencodeRuntime.ts+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { pathToFileURL } from "node:url";1import * as NodeURL from "node:url";
22
3import type { ChatAttachment, ProviderApprovalDecision, RuntimeMode } from "@t3tools/contracts";3import type { ChatAttachment, ProviderApprovalDecision, RuntimeMode } from "@t3tools/contracts";
4import {4import {
...@@ -206,7 +206,7 @@ export function toOpenCodeFileParts(input: {...@@ -206,7 +206,7 @@ export function toOpenCodeFileParts(input: {
206 type: "file",206 type: "file",
207 mime: attachment.mimeType,207 mime: attachment.mimeType,
208 filename: attachment.name,208 filename: attachment.name,
209 url: pathToFileURL(attachmentPath).href,209 url: NodeURL.pathToFileURL(attachmentPath).href,
210 });210 });
211 }211 }
212212
apps/server/src/provider/providerMaintenance.test.ts+44-44
...@@ -1,9 +1,9 @@...@@ -1,9 +1,9 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { expect, it } from "@effect/vitest";2import { expect, it } from "@effect/vitest";
3import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs";3import * as NodeFS from "node:fs";
4import * as NodeServices from "@effect/platform-node/NodeServices";4import * as NodeServices from "@effect/platform-node/NodeServices";
5import * as NodeOS from "node:os";5import * as NodeOS from "node:os";
6import path from "node:path";6import * as NodePath from "node:path";
7import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";7import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";
8import { HostProcessPlatform } from "@t3tools/shared/hostProcess";8import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
9import * as Crypto from "effect/Crypto";9import * as Crypto from "effect/Crypto";
...@@ -25,7 +25,7 @@ const driver = (value: string) => ProviderDriverKind.make(value);...@@ -25,7 +25,7 @@ const driver = (value: string) => ProviderDriverKind.make(value);
25const makeTempDir = (name: string) =>25const makeTempDir = (name: string) =>
26 Crypto.Crypto.pipe(26 Crypto.Crypto.pipe(
27 Effect.flatMap((crypto) => crypto.randomUUIDv4),27 Effect.flatMap((crypto) => crypto.randomUUIDv4),
28 Effect.map((id) => path.join(NodeOS.tmpdir(), `${name}-${id}`)),28 Effect.map((id) => NodePath.join(NodeOS.tmpdir(), `${name}-${id}`)),
29 );29 );
30const isNativeTestCommandPath =30const isNativeTestCommandPath =
31 (expectedPathSegment: string) =>31 (expectedPathSegment: string) =>
...@@ -203,11 +203,11 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {...@@ -203,11 +203,11 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
203 () =>203 () =>
204 Effect.gen(function* () {204 Effect.gen(function* () {
205 const tempDir = yield* makeTempDir("t3-vite-plus-capabilities");205 const tempDir = yield* makeTempDir("t3-vite-plus-capabilities");
206 const vitePlusBinDir = path.join(tempDir, ".vite-plus", "bin");206 const vitePlusBinDir = NodePath.join(tempDir, ".vite-plus", "bin");
207 mkdirSync(vitePlusBinDir, { recursive: true });207 NodeFS.mkdirSync(vitePlusBinDir, { recursive: true });
208 const packageToolPath = path.join(vitePlusBinDir, "package-tool");208 const packageToolPath = NodePath.join(vitePlusBinDir, "package-tool");
209 writeFileSync(packageToolPath, "#!/bin/sh\n");209 NodeFS.writeFileSync(packageToolPath, "#!/bin/sh\n");
210 chmodSync(packageToolPath, 0o755);210 NodeFS.chmodSync(packageToolPath, 0o755);
211211
212 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(212 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(
213 packageToolUpdate,213 packageToolUpdate,
...@@ -240,9 +240,9 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {...@@ -240,9 +240,9 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
240 () =>240 () =>
241 Effect.gen(function* () {241 Effect.gen(function* () {
242 const tempDir = yield* makeTempDir("t3-bun-capabilities");242 const tempDir = yield* makeTempDir("t3-bun-capabilities");
243 const bunBinDir = path.join(tempDir, ".bun", "bin");243 const bunBinDir = NodePath.join(tempDir, ".bun", "bin");
244 mkdirSync(bunBinDir, { recursive: true });244 NodeFS.mkdirSync(bunBinDir, { recursive: true });
245 writeFileSync(path.join(bunBinDir, "native-package-tool.exe"), "MZ");245 NodeFS.writeFileSync(NodePath.join(bunBinDir, "native-package-tool.exe"), "MZ");
246246
247 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(247 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(
248 nativePackageToolUpdate,248 nativePackageToolUpdate,
...@@ -276,11 +276,11 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {...@@ -276,11 +276,11 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
276 () =>276 () =>
277 Effect.gen(function* () {277 Effect.gen(function* () {
278 const tempDir = yield* makeTempDir("t3-pnpm-capabilities");278 const tempDir = yield* makeTempDir("t3-pnpm-capabilities");
279 const pnpmHomeDir = path.join(tempDir, ".local", "share", "pnpm");279 const pnpmHomeDir = NodePath.join(tempDir, ".local", "share", "pnpm");
280 mkdirSync(pnpmHomeDir, { recursive: true });280 NodeFS.mkdirSync(pnpmHomeDir, { recursive: true });
281 const scopedPackageToolPath = path.join(pnpmHomeDir, "scoped-package-tool");281 const scopedPackageToolPath = NodePath.join(pnpmHomeDir, "scoped-package-tool");
282 writeFileSync(scopedPackageToolPath, "#!/bin/sh\n");282 NodeFS.writeFileSync(scopedPackageToolPath, "#!/bin/sh\n");
283 chmodSync(scopedPackageToolPath, 0o755);283 NodeFS.chmodSync(scopedPackageToolPath, 0o755);
284284
285 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(285 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(
286 scopedPackageToolUpdate,286 scopedPackageToolUpdate,
...@@ -336,11 +336,11 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {...@@ -336,11 +336,11 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
336 () =>336 () =>
337 Effect.gen(function* () {337 Effect.gen(function* () {
338 const tempDir = yield* makeTempDir("t3-native-package-tool-native-capabilities");338 const tempDir = yield* makeTempDir("t3-native-package-tool-native-capabilities");
339 const nativeBinDir = path.join(tempDir, ".local", "bin");339 const nativeBinDir = NodePath.join(tempDir, ".local", "bin");
340 mkdirSync(nativeBinDir, { recursive: true });340 NodeFS.mkdirSync(nativeBinDir, { recursive: true });
341 const nativePackageToolPath = path.join(nativeBinDir, "native-package-tool");341 const nativePackageToolPath = NodePath.join(nativeBinDir, "native-package-tool");
342 writeFileSync(nativePackageToolPath, "#!/bin/sh\n");342 NodeFS.writeFileSync(nativePackageToolPath, "#!/bin/sh\n");
343 chmodSync(nativePackageToolPath, 0o755);343 NodeFS.chmodSync(nativePackageToolPath, 0o755);
344344
345 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(345 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(
346 nativePackageToolUpdate,346 nativePackageToolUpdate,
...@@ -373,11 +373,11 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {...@@ -373,11 +373,11 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
373 () =>373 () =>
374 Effect.gen(function* () {374 Effect.gen(function* () {
375 const tempDir = yield* makeTempDir("t3-scoped-package-tool-native-capabilities");375 const tempDir = yield* makeTempDir("t3-scoped-package-tool-native-capabilities");
376 const nativeBinDir = path.join(tempDir, ".scoped-package-tool", "bin");376 const nativeBinDir = NodePath.join(tempDir, ".scoped-package-tool", "bin");
377 mkdirSync(nativeBinDir, { recursive: true });377 NodeFS.mkdirSync(nativeBinDir, { recursive: true });
378 const scopedPackageToolPath = path.join(nativeBinDir, "scoped-package-tool");378 const scopedPackageToolPath = NodePath.join(nativeBinDir, "scoped-package-tool");
379 writeFileSync(scopedPackageToolPath, "#!/bin/sh\n");379 NodeFS.writeFileSync(scopedPackageToolPath, "#!/bin/sh\n");
380 chmodSync(scopedPackageToolPath, 0o755);380 NodeFS.chmodSync(scopedPackageToolPath, 0o755);
381381
382 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(382 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(
383 scopedPackageToolUpdate,383 scopedPackageToolUpdate,
...@@ -454,8 +454,8 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {...@@ -454,8 +454,8 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
454 it.effect("keeps npm updates for binaries symlinked into npm's global node_modules tree", () =>454 it.effect("keeps npm updates for binaries symlinked into npm's global node_modules tree", () =>
455 Effect.gen(function* () {455 Effect.gen(function* () {
456 const tempDir = yield* makeTempDir("t3-npm-capabilities");456 const tempDir = yield* makeTempDir("t3-npm-capabilities");
457 const binDir = path.join(tempDir, "bin");457 const binDir = NodePath.join(tempDir, "bin");
458 const packageBinDir = path.join(458 const packageBinDir = NodePath.join(
459 tempDir,459 tempDir,
460 "lib",460 "lib",
461 "node_modules",461 "node_modules",
...@@ -463,13 +463,13 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {...@@ -463,13 +463,13 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
463 "package-tool",463 "package-tool",
464 "bin",464 "bin",
465 );465 );
466 mkdirSync(binDir, { recursive: true });466 NodeFS.mkdirSync(binDir, { recursive: true });
467 mkdirSync(packageBinDir, { recursive: true });467 NodeFS.mkdirSync(packageBinDir, { recursive: true });
468 const packageBinPath = path.join(packageBinDir, "package-tool.js");468 const packageBinPath = NodePath.join(packageBinDir, "package-tool.js");
469 const symlinkPath = path.join(binDir, "package-tool");469 const symlinkPath = NodePath.join(binDir, "package-tool");
470 writeFileSync(packageBinPath, "#!/usr/bin/env node\n");470 NodeFS.writeFileSync(packageBinPath, "#!/usr/bin/env node\n");
471 chmodSync(packageBinPath, 0o755);471 NodeFS.chmodSync(packageBinPath, 0o755);
472 symlinkSync(packageBinPath, symlinkPath);472 NodeFS.symlinkSync(packageBinPath, symlinkPath);
473473
474 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(packageToolUpdate, {474 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(packageToolUpdate, {
475 binaryPath: symlinkPath,475 binaryPath: symlinkPath,
...@@ -497,8 +497,8 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {...@@ -497,8 +497,8 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
497 it.effect("uses Effect FileSystem realPath when detecting pnpm global symlinks", () =>497 it.effect("uses Effect FileSystem realPath when detecting pnpm global symlinks", () =>
498 Effect.gen(function* () {498 Effect.gen(function* () {
499 const tempDir = yield* makeTempDir("t3-pnpm-realpath-capabilities");499 const tempDir = yield* makeTempDir("t3-pnpm-realpath-capabilities");
500 const binDir = path.join(tempDir, "bin");500 const binDir = NodePath.join(tempDir, "bin");
501 const packageBinDir = path.join(501 const packageBinDir = NodePath.join(
502 tempDir,502 tempDir,
503 ".local",503 ".local",
504 "share",504 "share",
...@@ -510,13 +510,13 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {...@@ -510,13 +510,13 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
510 "package-tool",510 "package-tool",
511 "bin",511 "bin",
512 );512 );
513 mkdirSync(binDir, { recursive: true });513 NodeFS.mkdirSync(binDir, { recursive: true });
514 mkdirSync(packageBinDir, { recursive: true });514 NodeFS.mkdirSync(packageBinDir, { recursive: true });
515 const packageBinPath = path.join(packageBinDir, "package-tool.js");515 const packageBinPath = NodePath.join(packageBinDir, "package-tool.js");
516 const symlinkPath = path.join(binDir, "package-tool");516 const symlinkPath = NodePath.join(binDir, "package-tool");
517 writeFileSync(packageBinPath, "#!/usr/bin/env node\n");517 NodeFS.writeFileSync(packageBinPath, "#!/usr/bin/env node\n");
518 chmodSync(packageBinPath, 0o755);518 NodeFS.chmodSync(packageBinPath, 0o755);
519 symlinkSync(packageBinPath, symlinkPath);519 NodeFS.symlinkSync(packageBinPath, symlinkPath);
520520
521 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(packageToolUpdate, {521 const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(packageToolUpdate, {
522 binaryPath: symlinkPath,522 binaryPath: symlinkPath,
apps/server/src/relay/AgentAwarenessRelay.test.ts+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { generateKeyPairSync } from "node:crypto";1import * as NodeCrypto from "node:crypto";
2import * as NodeServices from "@effect/platform-node/NodeServices";2import * as NodeServices from "@effect/platform-node/NodeServices";
33
4import type {4import type {
...@@ -348,7 +348,7 @@ describe.sequential("signRelayAgentActivityPublishProof", () => {...@@ -348,7 +348,7 @@ describe.sequential("signRelayAgentActivityPublishProof", () => {
348 });348 });
349349
350 it("signs the activity publish JWT and rejects tampering", async () => {350 it("signs the activity publish JWT and rejects tampering", async () => {
351 const keyPair = generateKeyPairSync("ed25519", {351 const keyPair = NodeCrypto.generateKeyPairSync("ed25519", {
352 privateKeyEncoding: { format: "pem", type: "pkcs8" },352 privateKeyEncoding: { format: "pem", type: "pkcs8" },
353 publicKeyEncoding: { format: "pem", type: "spki" },353 publicKeyEncoding: { format: "pem", type: "spki" },
354 });354 });
apps/server/src/server.test.ts+22-22
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";1import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
2import * as NodeSocket from "@effect/platform-node/NodeSocket";2import * as NodeSocket from "@effect/platform-node/NodeSocket";
3import * as NodeServices from "@effect/platform-node/NodeServices";3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { generateKeyPairSync, type KeyObject, sign } from "node:crypto";4import * as NodeCrypto from "node:crypto";
55
6import {6import {
7 AuthAccessTokenType,7 AuthAccessTokenType,
...@@ -950,14 +950,14 @@ const makeDpopProof = (input: {...@@ -950,14 +950,14 @@ const makeDpopProof = (input: {
950 readonly iat: number;950 readonly iat: number;
951 readonly accessToken?: string;951 readonly accessToken?: string;
952 readonly jti?: string;952 readonly jti?: string;
953 readonly privateKey?: KeyObject;953 readonly privateKey?: NodeCrypto.KeyObject;
954 readonly publicJwk?: DpopPublicJwk;954 readonly publicJwk?: DpopPublicJwk;
955}) => {955}) => {
956 const keyPair =956 const keyPair =
957 input.privateKey && input.publicJwk957 input.privateKey && input.publicJwk
958 ? { privateKey: input.privateKey, publicJwk: input.publicJwk }958 ? { privateKey: input.privateKey, publicJwk: input.publicJwk }
959 : (() => {959 : (() => {
960 const { privateKey, publicKey } = generateKeyPairSync("ec", {960 const { privateKey, publicKey } = NodeCrypto.generateKeyPairSync("ec", {
961 namedCurve: "P-256",961 namedCurve: "P-256",
962 });962 });
963 return { privateKey, publicJwk: publicKey.export({ format: "jwk" }) as DpopPublicJwk };963 return { privateKey, publicJwk: publicKey.export({ format: "jwk" }) as DpopPublicJwk };
...@@ -978,7 +978,7 @@ const makeDpopProof = (input: {...@@ -978,7 +978,7 @@ const makeDpopProof = (input: {
978 ...(input.accessToken ? { ath: computeDpopAccessTokenHash(input.accessToken) } : {}),978 ...(input.accessToken ? { ath: computeDpopAccessTokenHash(input.accessToken) } : {}),
979 }),979 }),
980 ).toString("base64url");980 ).toString("base64url");
981 const signature = sign("sha256", Buffer.from(`${header}.${payload}`), {981 const signature = NodeCrypto.sign("sha256", Buffer.from(`${header}.${payload}`), {
982 key: keyPair.privateKey,982 key: keyPair.privateKey,
983 dsaEncoding: "ieee-p1363",983 dsaEncoding: "ieee-p1363",
984 }).toString("base64url");984 }).toString("base64url");
...@@ -1024,7 +1024,7 @@ const makeCloudMintCredentialRequest = (input: {...@@ -1024,7 +1024,7 @@ const makeCloudMintCredentialRequest = (input: {
1024 const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");1024 const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
1025 const signingInput = `${header}.${encodedPayload}`;1025 const signingInput = `${header}.${encodedPayload}`;
1026 return {1026 return {
1027 proof: `${signingInput}.${sign(null, Buffer.from(signingInput), input.privateKey).toString("base64url")}`,1027 proof: `${signingInput}.${NodeCrypto.sign(null, Buffer.from(signingInput), input.privateKey).toString("base64url")}`,
1028 };1028 };
1029};1029};
10301030
...@@ -1057,7 +1057,7 @@ const makeCloudEnvironmentHealthRequest = (input: {...@@ -1057,7 +1057,7 @@ const makeCloudEnvironmentHealthRequest = (input: {
1057 const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");1057 const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
1058 const signingInput = `${header}.${encodedPayload}`;1058 const signingInput = `${header}.${encodedPayload}`;
1059 return {1059 return {
1060 proof: `${signingInput}.${sign(null, Buffer.from(signingInput), input.privateKey).toString("base64url")}`,1060 proof: `${signingInput}.${NodeCrypto.sign(null, Buffer.from(signingInput), input.privateKey).toString("base64url")}`,
1061 };1061 };
1062};1062};
10631063
...@@ -2054,7 +2054,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2054,7 +2054,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2054 Effect.gen(function* () {2054 Effect.gen(function* () {
2055 yield* buildAppUnderTest();2055 yield* buildAppUnderTest();
20562056
2057 const cloudKeyPair = generateKeyPairSync("ed25519", {2057 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2058 privateKeyEncoding: { format: "pem", type: "pkcs8" },2058 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2059 publicKeyEncoding: { format: "pem", type: "spki" },2059 publicKeyEncoding: { format: "pem", type: "spki" },
2060 });2060 });
...@@ -2131,7 +2131,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2131,7 +2131,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2131 Effect.gen(function* () {2131 Effect.gen(function* () {
2132 yield* buildAppUnderTest();2132 yield* buildAppUnderTest();
21332133
2134 const cloudKeyPair = generateKeyPairSync("ed25519", {2134 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2135 privateKeyEncoding: { format: "pem", type: "pkcs8" },2135 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2136 publicKeyEncoding: { format: "pem", type: "spki" },2136 publicKeyEncoding: { format: "pem", type: "spki" },
2137 });2137 });
...@@ -2174,7 +2174,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2174,7 +2174,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2174 Effect.gen(function* () {2174 Effect.gen(function* () {
2175 yield* buildAppUnderTest();2175 yield* buildAppUnderTest();
21762176
2177 const cloudKeyPair = generateKeyPairSync("ed25519", {2177 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2178 privateKeyEncoding: { format: "pem", type: "pkcs8" },2178 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2179 publicKeyEncoding: { format: "pem", type: "spki" },2179 publicKeyEncoding: { format: "pem", type: "spki" },
2180 });2180 });
...@@ -2268,7 +2268,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2268,7 +2268,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2268 },2268 },
2269 });2269 });
22702270
2271 const cloudKeyPair = generateKeyPairSync("ed25519", {2271 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2272 privateKeyEncoding: { format: "pem", type: "pkcs8" },2272 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2273 publicKeyEncoding: { format: "pem", type: "spki" },2273 publicKeyEncoding: { format: "pem", type: "spki" },
2274 });2274 });
...@@ -2345,7 +2345,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2345,7 +2345,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2345 Effect.gen(function* () {2345 Effect.gen(function* () {
2346 yield* buildAppUnderTest();2346 yield* buildAppUnderTest();
23472347
2348 const cloudKeyPair = generateKeyPairSync("ed25519", {2348 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2349 privateKeyEncoding: { format: "pem", type: "pkcs8" },2349 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2350 publicKeyEncoding: { format: "pem", type: "spki" },2350 publicKeyEncoding: { format: "pem", type: "spki" },
2351 });2351 });
...@@ -2404,7 +2404,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2404,7 +2404,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2404 Effect.gen(function* () {2404 Effect.gen(function* () {
2405 yield* buildAppUnderTest();2405 yield* buildAppUnderTest();
24062406
2407 const cloudKeyPair = generateKeyPairSync("ed25519", {2407 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2408 privateKeyEncoding: { format: "pem", type: "pkcs8" },2408 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2409 publicKeyEncoding: { format: "pem", type: "spki" },2409 publicKeyEncoding: { format: "pem", type: "spki" },
2410 });2410 });
...@@ -2463,7 +2463,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2463,7 +2463,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2463 Effect.gen(function* () {2463 Effect.gen(function* () {
2464 yield* buildAppUnderTest();2464 yield* buildAppUnderTest();
24652465
2466 const cloudKeyPair = generateKeyPairSync("ed25519", {2466 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2467 privateKeyEncoding: { format: "pem", type: "pkcs8" },2467 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2468 publicKeyEncoding: { format: "pem", type: "spki" },2468 publicKeyEncoding: { format: "pem", type: "spki" },
2469 });2469 });
...@@ -2523,7 +2523,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2523,7 +2523,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2523 Effect.gen(function* () {2523 Effect.gen(function* () {
2524 yield* buildAppUnderTest();2524 yield* buildAppUnderTest();
25252525
2526 const cloudKeyPair = generateKeyPairSync("ed25519", {2526 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2527 privateKeyEncoding: { format: "pem", type: "pkcs8" },2527 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2528 publicKeyEncoding: { format: "pem", type: "spki" },2528 publicKeyEncoding: { format: "pem", type: "spki" },
2529 });2529 });
...@@ -2584,7 +2584,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2584,7 +2584,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2584 Effect.gen(function* () {2584 Effect.gen(function* () {
2585 yield* buildAppUnderTest();2585 yield* buildAppUnderTest();
25862586
2587 const cloudKeyPair = generateKeyPairSync("ed25519", {2587 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2588 privateKeyEncoding: { format: "pem", type: "pkcs8" },2588 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2589 publicKeyEncoding: { format: "pem", type: "spki" },2589 publicKeyEncoding: { format: "pem", type: "spki" },
2590 });2590 });
...@@ -2664,7 +2664,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2664,7 +2664,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2664 },2664 },
2665 });2665 });
26662666
2667 const cloudKeyPair = generateKeyPairSync("ed25519", {2667 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2668 privateKeyEncoding: { format: "pem", type: "pkcs8" },2668 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2669 publicKeyEncoding: { format: "pem", type: "spki" },2669 publicKeyEncoding: { format: "pem", type: "spki" },
2670 });2670 });
...@@ -2733,7 +2733,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2733,7 +2733,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2733 Effect.gen(function* () {2733 Effect.gen(function* () {
2734 yield* buildAppUnderTest();2734 yield* buildAppUnderTest();
27352735
2736 const cloudKeyPair = generateKeyPairSync("ed25519", {2736 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2737 privateKeyEncoding: { format: "pem", type: "pkcs8" },2737 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2738 publicKeyEncoding: { format: "pem", type: "spki" },2738 publicKeyEncoding: { format: "pem", type: "spki" },
2739 });2739 });
...@@ -2800,7 +2800,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2800,7 +2800,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2800 Effect.gen(function* () {2800 Effect.gen(function* () {
2801 yield* buildAppUnderTest();2801 yield* buildAppUnderTest();
28022802
2803 const cloudKeyPair = generateKeyPairSync("ed25519", {2803 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2804 privateKeyEncoding: { format: "pem", type: "pkcs8" },2804 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2805 publicKeyEncoding: { format: "pem", type: "spki" },2805 publicKeyEncoding: { format: "pem", type: "spki" },
2806 });2806 });
...@@ -2851,7 +2851,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2851,7 +2851,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2851 Effect.gen(function* () {2851 Effect.gen(function* () {
2852 yield* buildAppUnderTest();2852 yield* buildAppUnderTest();
28532853
2854 const cloudKeyPair = generateKeyPairSync("ed25519", {2854 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2855 privateKeyEncoding: { format: "pem", type: "pkcs8" },2855 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2856 publicKeyEncoding: { format: "pem", type: "spki" },2856 publicKeyEncoding: { format: "pem", type: "spki" },
2857 });2857 });
...@@ -2902,7 +2902,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2902,7 +2902,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2902 Effect.gen(function* () {2902 Effect.gen(function* () {
2903 yield* buildAppUnderTest();2903 yield* buildAppUnderTest();
29042904
2905 const cloudKeyPair = generateKeyPairSync("ed25519", {2905 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2906 privateKeyEncoding: { format: "pem", type: "pkcs8" },2906 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2907 publicKeyEncoding: { format: "pem", type: "spki" },2907 publicKeyEncoding: { format: "pem", type: "spki" },
2908 });2908 });
...@@ -2967,7 +2967,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -2967,7 +2967,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
2967 Effect.gen(function* () {2967 Effect.gen(function* () {
2968 yield* buildAppUnderTest();2968 yield* buildAppUnderTest();
29692969
2970 const cloudKeyPair = generateKeyPairSync("ed25519", {2970 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
2971 privateKeyEncoding: { format: "pem", type: "pkcs8" },2971 privateKeyEncoding: { format: "pem", type: "pkcs8" },
2972 publicKeyEncoding: { format: "pem", type: "spki" },2972 publicKeyEncoding: { format: "pem", type: "spki" },
2973 });2973 });
...@@ -3017,7 +3017,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {...@@ -3017,7 +3017,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
3017 Effect.gen(function* () {3017 Effect.gen(function* () {
3018 yield* buildAppUnderTest();3018 yield* buildAppUnderTest();
30193019
3020 const cloudKeyPair = generateKeyPairSync("ed25519", {3020 const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", {
3021 privateKeyEncoding: { format: "pem", type: "pkcs8" },3021 privateKeyEncoding: { format: "pem", type: "pkcs8" },
3022 publicKeyEncoding: { format: "pem", type: "spki" },3022 publicKeyEncoding: { format: "pem", type: "spki" },
3023 });3023 });
apps/server/src/terminal/NodePtyAdapter.ts+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { createRequire } from "node:module";1import * as NodeModule from "node:module";
22
3import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";4import * as FileSystem from "effect/FileSystem";
...@@ -11,7 +11,7 @@ import * as PtyAdapter from "./PtyAdapter.ts";...@@ -11,7 +11,7 @@ import * as PtyAdapter from "./PtyAdapter.ts";
11let didEnsureSpawnHelperExecutable = false;11let didEnsureSpawnHelperExecutable = false;
1212
13const resolveNodePtySpawnHelperPath = Effect.gen(function* () {13const resolveNodePtySpawnHelperPath = Effect.gen(function* () {
14 const requireForNodePty = createRequire(import.meta.url);14 const requireForNodePty = NodeModule.createRequire(import.meta.url);
15 const path = yield* Path.Path;15 const path = yield* Path.Path;
16 const fs = yield* FileSystem.FileSystem;16 const fs = yield* FileSystem.FileSystem;
17 const platform = yield* HostProcessPlatform;17 const platform = yield* HostProcessPlatform;
apps/server/src/textGeneration/CursorTextGeneration.test.ts+25-21
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import * as path from "node:path";2import * as NodePath from "node:path";
3import * as os from "node:os";3import * as NodeOS from "node:os";
4import { fileURLToPath } from "node:url";4import * as NodeURL from "node:url";
5import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";5import * as NodeFS from "node:fs";
66
7import * as NodeServices from "@effect/platform-node/NodeServices";7import * as NodeServices from "@effect/platform-node/NodeServices";
8import { it } from "@effect/vitest";8import { it } from "@effect/vitest";
...@@ -21,8 +21,8 @@ import * as TextGeneration from "./TextGeneration.ts";...@@ -21,8 +21,8 @@ import * as TextGeneration from "./TextGeneration.ts";
21import { makeCursorTextGeneration } from "./CursorTextGeneration.ts";21import { makeCursorTextGeneration } from "./CursorTextGeneration.ts";
22const decodeCursorSettings = Schema.decodeSync(CursorSettings);22const decodeCursorSettings = Schema.decodeSync(CursorSettings);
2323
24const __dirname = path.dirname(fileURLToPath(import.meta.url));24const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
25const mockAgentPath = path.join(__dirname, "../../scripts/acp-mock-agent.ts");25const mockAgentPath = NodePath.join(__dirname, "../../scripts/acp-mock-agent.ts");
2626
27function shellSingleQuote(value: string): string {27function shellSingleQuote(value: string): string {
28 return `'${value.replaceAll("'", `'"'"'`)}'`;28 return `'${value.replaceAll("'", `'"'"'`)}'`;
...@@ -33,10 +33,10 @@ const CursorTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(proces...@@ -33,10 +33,10 @@ const CursorTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(proces
33}).pipe(Layer.provideMerge(NodeServices.layer));33}).pipe(Layer.provideMerge(NodeServices.layer));
3434
35function makeAcpAgentWrapper(dir: string, env: Record<string, string>): string {35function makeAcpAgentWrapper(dir: string, env: Record<string, string>): string {
36 const binDir = path.join(dir, "bin");36 const binDir = NodePath.join(dir, "bin");
37 const agentPath = path.join(binDir, "agent");37 const agentPath = NodePath.join(binDir, "agent");
38 mkdirSync(binDir, { recursive: true });38 NodeFS.mkdirSync(binDir, { recursive: true });
39 writeFileSync(39 NodeFS.writeFileSync(
40 agentPath,40 agentPath,
41 [41 [
42 "#!/bin/sh",42 "#!/bin/sh",
...@@ -50,7 +50,7 @@ function makeAcpAgentWrapper(dir: string, env: Record<string, string>): string {...@@ -50,7 +50,7 @@ function makeAcpAgentWrapper(dir: string, env: Record<string, string>): string {
50 ].join("\n"),50 ].join("\n"),
51 "utf8",51 "utf8",
52 );52 );
53 chmodSync(agentPath, 0o755);53 NodeFS.chmodSync(agentPath, 0o755);
54 return agentPath;54 return agentPath;
55}55}
5656
...@@ -59,10 +59,10 @@ function withFakeAcpAgent<A, E, R>(...@@ -59,10 +59,10 @@ function withFakeAcpAgent<A, E, R>(
59 effectFn: (textGeneration: TextGeneration.TextGeneration["Service"]) => Effect.Effect<A, E, R>,59 effectFn: (textGeneration: TextGeneration.TextGeneration["Service"]) => Effect.Effect<A, E, R>,
60) {60) {
61 return Effect.gen(function* () {61 return Effect.gen(function* () {
62 const tempDir = mkdtempSync(path.join(os.tmpdir(), "t3code-cursor-text-acp-"));62 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3code-cursor-text-acp-"));
63 yield* Effect.addFinalizer(() =>63 yield* Effect.addFinalizer(() =>
64 Effect.sync(() => {64 Effect.sync(() => {
65 rmSync(tempDir, { recursive: true, force: true });65 NodeFS.rmSync(tempDir, { recursive: true, force: true });
66 }),66 }),
67 );67 );
68 const agentPath = makeAcpAgentWrapper(tempDir, env);68 const agentPath = makeAcpAgentWrapper(tempDir, env);
...@@ -76,7 +76,7 @@ function waitForFileContent(path: string): Effect.Effect<string> {...@@ -76,7 +76,7 @@ function waitForFileContent(path: string): Effect.Effect<string> {
76 return Effect.gen(function* () {76 return Effect.gen(function* () {
77 const deadline = (yield* Clock.currentTimeMillis) + 5_000;77 const deadline = (yield* Clock.currentTimeMillis) + 5_000;
78 for (;;) {78 for (;;) {
79 const result = yield* Effect.exit(Effect.sync(() => readFileSync(path, "utf8")));79 const result = yield* Effect.exit(Effect.sync(() => NodeFS.readFileSync(path, "utf8")));
80 if (Exit.isSuccess(result)) {80 if (Exit.isSuccess(result)) {
81 return result.value;81 return result.value;
82 }82 }
...@@ -92,8 +92,10 @@ function waitForFileContent(path: string): Effect.Effect<string> {...@@ -92,8 +92,10 @@ function waitForFileContent(path: string): Effect.Effect<string> {
9292
93it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {93it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {
94 it.effect("uses ACP model config options instead of raw CLI model ids", () => {94 it.effect("uses ACP model config options instead of raw CLI model ids", () => {
95 const requestLogDir = mkdtempSync(path.join(os.tmpdir(), "t3code-cursor-text-log-"));95 const requestLogDir = NodeFS.mkdtempSync(
96 const requestLogPath = path.join(requestLogDir, "requests.ndjson");96 NodePath.join(NodeOS.tmpdir(), "t3code-cursor-text-log-"),
97 );
98 const requestLogPath = NodePath.join(requestLogDir, "requests.ndjson");
9799
98 return withFakeAcpAgent(100 return withFakeAcpAgent(
99 {101 {
...@@ -123,7 +125,7 @@ it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {...@@ -123,7 +125,7 @@ it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {
123 expect(generated.subject).toBe("Add generated commit message");125 expect(generated.subject).toBe("Add generated commit message");
124 expect(generated.body).toBe("- verify cursor acp model config path");126 expect(generated.body).toBe("- verify cursor acp model config path");
125127
126 const requests = readFileSync(requestLogPath, "utf8")128 const requests = NodeFS.readFileSync(requestLogPath, "utf8")
127 .trim()129 .trim()
128 .split("\n")130 .split("\n")
129 .filter((line) => line.length > 0)131 .filter((line) => line.length > 0)
...@@ -181,7 +183,7 @@ it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {...@@ -181,7 +183,7 @@ it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {
181 ]),183 ]),
182 );184 );
183185
184 rmSync(requestLogDir, { recursive: true, force: true });186 NodeFS.rmSync(requestLogDir, { recursive: true, force: true });
185 }),187 }),
186 );188 );
187 });189 });
...@@ -235,8 +237,10 @@ it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {...@@ -235,8 +237,10 @@ it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {
235 );237 );
236238
237 it.effect("closes the ACP child process after text generation completes", () => {239 it.effect("closes the ACP child process after text generation completes", () => {
238 const exitLogDir = mkdtempSync(path.join(os.tmpdir(), "t3code-cursor-text-exit-log-"));240 const exitLogDir = NodeFS.mkdtempSync(
239 const exitLogPath = path.join(exitLogDir, "exit.log");241 NodePath.join(NodeOS.tmpdir(), "t3code-cursor-text-exit-log-"),
242 );
243 const exitLogPath = NodePath.join(exitLogDir, "exit.log");
240244
241 return withFakeAcpAgent(245 return withFakeAcpAgent(
242 {246 {
...@@ -265,7 +269,7 @@ it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {...@@ -265,7 +269,7 @@ it.layer(CursorTextGenerationTestLayer)("CursorTextGeneration", (it) => {
265 const exitLog = yield* waitForFileContent(exitLogPath);269 const exitLog = yield* waitForFileContent(exitLogPath);
266 expect(exitLog).toContain("exit:0");270 expect(exitLog).toContain("exit:0");
267271
268 rmSync(exitLogDir, { recursive: true, force: true });272 NodeFS.rmSync(exitLogDir, { recursive: true, force: true });
269 }),273 }),
270 );274 );
271 });275 });
apps/server/src/textGeneration/GrokTextGeneration.test.ts+18-16
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import * as path from "node:path";2import * as NodePath from "node:path";
3import * as os from "node:os";3import * as NodeOS from "node:os";
4import { fileURLToPath } from "node:url";4import * as NodeURL from "node:url";
5import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";5import * as NodeFS from "node:fs";
66
7import * as NodeServices from "@effect/platform-node/NodeServices";7import * as NodeServices from "@effect/platform-node/NodeServices";
8import { it } from "@effect/vitest";8import { it } from "@effect/vitest";
...@@ -18,8 +18,8 @@ import * as TextGeneration from "./TextGeneration.ts";...@@ -18,8 +18,8 @@ import * as TextGeneration from "./TextGeneration.ts";
18import { makeGrokTextGeneration } from "./GrokTextGeneration.ts";18import { makeGrokTextGeneration } from "./GrokTextGeneration.ts";
19const decodeGrokSettings = Schema.decodeSync(GrokSettings);19const decodeGrokSettings = Schema.decodeSync(GrokSettings);
2020
21const __dirname = path.dirname(fileURLToPath(import.meta.url));21const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
22const mockAgentPath = path.join(__dirname, "../../scripts/acp-mock-agent.ts");22const mockAgentPath = NodePath.join(__dirname, "../../scripts/acp-mock-agent.ts");
2323
24function shellSingleQuote(value: string): string {24function shellSingleQuote(value: string): string {
25 return `'${value.replaceAll("'", `'"'"'`)}'`;25 return `'${value.replaceAll("'", `'"'"'`)}'`;
...@@ -30,10 +30,10 @@ const GrokTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process....@@ -30,10 +30,10 @@ const GrokTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.
30}).pipe(Layer.provideMerge(NodeServices.layer));30}).pipe(Layer.provideMerge(NodeServices.layer));
3131
32function makeAcpGrokWrapper(dir: string, env: Record<string, string>): string {32function makeAcpGrokWrapper(dir: string, env: Record<string, string>): string {
33 const binDir = path.join(dir, "bin");33 const binDir = NodePath.join(dir, "bin");
34 const grokPath = path.join(binDir, "grok");34 const grokPath = NodePath.join(binDir, "grok");
35 mkdirSync(binDir, { recursive: true });35 NodeFS.mkdirSync(binDir, { recursive: true });
36 writeFileSync(36 NodeFS.writeFileSync(
37 grokPath,37 grokPath,
38 [38 [
39 "#!/bin/sh",39 "#!/bin/sh",
...@@ -47,7 +47,7 @@ function makeAcpGrokWrapper(dir: string, env: Record<string, string>): string {...@@ -47,7 +47,7 @@ function makeAcpGrokWrapper(dir: string, env: Record<string, string>): string {
47 ].join("\n"),47 ].join("\n"),
48 "utf8",48 "utf8",
49 );49 );
50 chmodSync(grokPath, 0o755);50 NodeFS.chmodSync(grokPath, 0o755);
51 return grokPath;51 return grokPath;
52}52}
5353
...@@ -56,10 +56,10 @@ function withFakeAcpGrok<A, E, R>(...@@ -56,10 +56,10 @@ function withFakeAcpGrok<A, E, R>(
56 effectFn: (textGeneration: TextGeneration.TextGeneration["Service"]) => Effect.Effect<A, E, R>,56 effectFn: (textGeneration: TextGeneration.TextGeneration["Service"]) => Effect.Effect<A, E, R>,
57) {57) {
58 return Effect.gen(function* () {58 return Effect.gen(function* () {
59 const tempDir = mkdtempSync(path.join(os.tmpdir(), "t3code-grok-text-acp-"));59 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3code-grok-text-acp-"));
60 yield* Effect.addFinalizer(() =>60 yield* Effect.addFinalizer(() =>
61 Effect.sync(() => {61 Effect.sync(() => {
62 rmSync(tempDir, { recursive: true, force: true });62 NodeFS.rmSync(tempDir, { recursive: true, force: true });
63 }),63 }),
64 );64 );
65 const binaryPath = makeAcpGrokWrapper(tempDir, env);65 const binaryPath = makeAcpGrokWrapper(tempDir, env);
...@@ -72,7 +72,7 @@ function withFakeAcpGrok<A, E, R>(...@@ -72,7 +72,7 @@ function withFakeAcpGrok<A, E, R>(
72function readJsonRpcRequests(72function readJsonRpcRequests(
73 filePath: string,73 filePath: string,
74): ReadonlyArray<{ readonly method?: string; readonly params?: Record<string, unknown> }> {74): ReadonlyArray<{ readonly method?: string; readonly params?: Record<string, unknown> }> {
75 return readFileSync(filePath, "utf8")75 return NodeFS.readFileSync(filePath, "utf8")
76 .trim()76 .trim()
77 .split("\n")77 .split("\n")
78 .filter((line) => line.length > 0)78 .filter((line) => line.length > 0)
...@@ -81,8 +81,10 @@ function readJsonRpcRequests(...@@ -81,8 +81,10 @@ function readJsonRpcRequests(
8181
82it.layer(GrokTextGenerationTestLayer)("GrokTextGeneration", (it) => {82it.layer(GrokTextGenerationTestLayer)("GrokTextGeneration", (it) => {
83 it.effect("uses ACP with disabled tool capabilities and forwards the requested model id", () => {83 it.effect("uses ACP with disabled tool capabilities and forwards the requested model id", () => {
84 const requestLogDir = mkdtempSync(path.join(os.tmpdir(), "t3code-grok-text-log-"));84 const requestLogDir = NodeFS.mkdtempSync(
85 const requestLogPath = path.join(requestLogDir, "requests.ndjson");85 NodePath.join(NodeOS.tmpdir(), "t3code-grok-text-log-"),
86 );
87 const requestLogPath = NodePath.join(requestLogDir, "requests.ndjson");
8688
87 return withFakeAcpGrok(89 return withFakeAcpGrok(
88 {90 {
apps/server/src/vcs/GitVcsDriver.ts+5-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { randomUUID } from "node:crypto";1import * as NodeCrypto from "node:crypto";
22
3import * as Context from "effect/Context";3import * as Context from "effect/Context";
4import * as DateTime from "effect/DateTime";4import * as DateTime from "effect/DateTime";
...@@ -651,7 +651,10 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (...@@ -651,7 +651,10 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (
651 captureCheckpoint: Effect.fn("GitVcsDriver.checkpoints.captureCheckpoint")(function* (input) {651 captureCheckpoint: Effect.fn("GitVcsDriver.checkpoints.captureCheckpoint")(function* (input) {
652 const operation = "GitVcsDriver.checkpoints.captureCheckpoint";652 const operation = "GitVcsDriver.checkpoints.captureCheckpoint";
653 const gitCommonDir = yield* resolveGitCommonDir(input.cwd);653 const gitCommonDir = yield* resolveGitCommonDir(input.cwd);
654 const tempIndexPath = path.join(gitCommonDir, `t3-checkpoint-index-${randomUUID()}`);654 const tempIndexPath = path.join(
655 gitCommonDir,
656 `t3-checkpoint-index-${NodeCrypto.randomUUID()}`,
657 );
655 const commitEnv: NodeJS.ProcessEnv = {658 const commitEnv: NodeJS.ProcessEnv = {
656 ...process.env,659 ...process.env,
657 GIT_INDEX_FILE: tempIndexPath,660 GIT_INDEX_FILE: tempIndexPath,
apps/server/src/workspace/WorkspaceEntries.test.ts+9-3
...@@ -1,13 +1,14 @@...@@ -1,13 +1,14 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fsPromises from "node:fs/promises";2import * as NodeFSP from "node:fs/promises";
3import * as NodeServices from "@effect/platform-node/NodeServices";3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { FileFinder } from "@ff-labs/fff-node";4import { FileFinder } from "@ff-labs/fff-node";
5import { it, afterEach, describe, expect, vi } from "@effect/vitest";5import { it, afterEach, describe, expect } from "@effect/vitest";
6import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
7import * as FileSystem from "effect/FileSystem";7import * as FileSystem from "effect/FileSystem";
8import * as Layer from "effect/Layer";8import * as Layer from "effect/Layer";
9import * as Path from "effect/Path";9import * as Path from "effect/Path";
10import * as PlatformError from "effect/PlatformError";10import * as PlatformError from "effect/PlatformError";
11import { vi } from "vite-plus/test";
1112
12import * as ServerConfig from "../config.ts";13import * as ServerConfig from "../config.ts";
13import { HostProcessPlatform } from "@t3tools/shared/hostProcess";14import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
...@@ -15,6 +16,11 @@ import * as VcsProcess from "../vcs/VcsProcess.ts";...@@ -15,6 +16,11 @@ import * as VcsProcess from "../vcs/VcsProcess.ts";
15import * as WorkspaceEntries from "./WorkspaceEntries.ts";16import * as WorkspaceEntries from "./WorkspaceEntries.ts";
16import * as WorkspacePaths from "./WorkspacePaths.ts";17import * as WorkspacePaths from "./WorkspacePaths.ts";
1718
19vi.mock("node:fs/promises", async (importOriginal) => {
20 const actual = await importOriginal<typeof import("node:fs/promises")>();
21 return { ...actual, readdir: vi.fn(actual.readdir) };
22});
23
18const TestLayer = Layer.empty.pipe(24const TestLayer = Layer.empty.pipe(
19 Layer.provideMerge(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))),25 Layer.provideMerge(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))),
20 Layer.provideMerge(WorkspacePaths.layer),26 Layer.provideMerge(WorkspacePaths.layer),
...@@ -376,7 +382,7 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceEntries", (it) => {...@@ -376,7 +382,7 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceEntries", (it) => {
376 const cwd = yield* makeTempDir({ prefix: "t3code-workspace-browse-eacces-" });382 const cwd = yield* makeTempDir({ prefix: "t3code-workspace-browse-eacces-" });
377383
378 const denied = Object.assign(new Error("EACCES: permission denied"), { code: "EACCES" });384 const denied = Object.assign(new Error("EACCES: permission denied"), { code: "EACCES" });
379 vi.spyOn(fsPromises, "readdir").mockRejectedValueOnce(denied);385 vi.mocked(NodeFSP.readdir).mockRejectedValueOnce(denied);
380386
381 const result = yield* workspaceEntries.browse({387 const result = yield* workspaceEntries.browse({
382 partialPath: yield* appendSeparator(cwd),388 partialPath: yield* appendSeparator(cwd),
apps/server/src/workspace/WorkspaceEntries.ts+5-5
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { readdir } from "node:fs/promises";2import * as NodeFSP from "node:fs/promises";
3import { homedir } from "node:os";3import * as NodeOS from "node:os";
44
5import * as Context from "effect/Context";5import * as Context from "effect/Context";
6import * as Effect from "effect/Effect";6import * as Effect from "effect/Effect";
...@@ -98,10 +98,10 @@ export class WorkspaceEntries extends Context.Service<...@@ -98,10 +98,10 @@ export class WorkspaceEntries extends Context.Service<
9898
99function expandHomePath(input: string, path: Path.Path): string {99function expandHomePath(input: string, path: Path.Path): string {
100 if (input === "~") {100 if (input === "~") {
101 return homedir();101 return NodeOS.homedir();
102 }102 }
103 if (input.startsWith("~/") || input.startsWith("~\\")) {103 if (input.startsWith("~/") || input.startsWith("~\\")) {
104 return path.join(homedir(), input.slice(2));104 return path.join(NodeOS.homedir(), input.slice(2));
105 }105 }
106 return input;106 return input;
107}107}
...@@ -176,7 +176,7 @@ export const make = Effect.gen(function* () {...@@ -176,7 +176,7 @@ export const make = Effect.gen(function* () {
176 const prefix = endsWithSeparator ? "" : path.basename(resolvedInputPath);176 const prefix = endsWithSeparator ? "" : path.basename(resolvedInputPath);
177177
178 const dirents = yield* Effect.tryPromise({178 const dirents = yield* Effect.tryPromise({
179 try: () => readdir(parentPath, { withFileTypes: true }),179 try: () => NodeFSP.readdir(parentPath, { withFileTypes: true }),
180 catch: (cause) =>180 catch: (cause) =>
181 new WorkspaceEntriesReadDirectoryError({181 new WorkspaceEntriesReadDirectoryError({
182 cwd: input.cwd,182 cwd: input.cwd,
apps/server/src/workspace/WorkspaceFileSystem.ts+4-4
...@@ -7,7 +7,7 @@...@@ -7,7 +7,7 @@
7 *7 *
8 * @module WorkspaceFileSystem8 * @module WorkspaceFileSystem
9 */9 */
10import { open, realpath } from "node:fs/promises";10import * as NodeFSP from "node:fs/promises";
1111
12import type {12import type {
13 ProjectReadFileInput,13 ProjectReadFileInput,
...@@ -89,8 +89,8 @@ export const make = Effect.gen(function* () {...@@ -89,8 +89,8 @@ export const make = Effect.gen(function* () {
89 return yield* Effect.tryPromise({89 return yield* Effect.tryPromise({
90 try: async () => {90 try: async () => {
91 const [realWorkspaceRoot, realTargetPath] = await Promise.all([91 const [realWorkspaceRoot, realTargetPath] = await Promise.all([
92 realpath(input.cwd),92 NodeFSP.realpath(input.cwd),
93 realpath(target.absolutePath),93 NodeFSP.realpath(target.absolutePath),
94 ]);94 ]);
95 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);95 const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath);
96 if (96 if (
...@@ -101,7 +101,7 @@ export const make = Effect.gen(function* () {...@@ -101,7 +101,7 @@ export const make = Effect.gen(function* () {
101 throw new Error("Workspace file path resolves outside the project root.");101 throw new Error("Workspace file path resolves outside the project root.");
102 }102 }
103103
104 const handle = await open(realTargetPath, "r");104 const handle = await NodeFSP.open(realTargetPath, "r");
105 try {105 try {
106 const stat = await handle.stat();106 const stat = await handle.stat();
107 if (!stat.isFile()) {107 if (!stat.isFile()) {
apps/server/src/workspace/WorkspacePaths.ts+3-3
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 *6 *
7 * @module WorkspacePaths7 * @module WorkspacePaths
8 */8 */
9import { homedir } from "node:os";9import * as NodeOS from "node:os";
1010
11import * as Context from "effect/Context";11import * as Context from "effect/Context";
12import * as Effect from "effect/Effect";12import * as Effect from "effect/Effect";
...@@ -105,10 +105,10 @@ function toPosixRelativePath(input: string): string {...@@ -105,10 +105,10 @@ function toPosixRelativePath(input: string): string {
105105
106function expandHomePath(input: string, path: Path.Path): string {106function expandHomePath(input: string, path: Path.Path): string {
107 if (input === "~") {107 if (input === "~") {
108 return homedir();108 return NodeOS.homedir();
109 }109 }
110 if (input.startsWith("~/") || input.startsWith("~\\")) {110 if (input.startsWith("~/") || input.startsWith("~\\")) {
111 return path.join(homedir(), input.slice(2));111 return path.join(NodeOS.homedir(), input.slice(2));
112 }112 }
113 return input;113 return input;
114}114}
oxlint-plugin-t3code/index.ts+2
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1import { definePlugin } from "@oxlint/plugins";1import { definePlugin } from "@oxlint/plugins";
22
3import namespaceNodeImports from "./rules/namespace-node-imports.ts";
3import noGlobalProcessRuntime from "./rules/no-global-process-runtime.ts";4import noGlobalProcessRuntime from "./rules/no-global-process-runtime.ts";
4import noInlineSchemaCompile from "./rules/no-inline-schema-compile.ts";5import noInlineSchemaCompile from "./rules/no-inline-schema-compile.ts";
5import noManualEffectRuntimeInTests from "./rules/no-manual-effect-runtime-in-tests.ts";6import noManualEffectRuntimeInTests from "./rules/no-manual-effect-runtime-in-tests.ts";
...@@ -9,6 +10,7 @@ export default definePlugin({...@@ -9,6 +10,7 @@ export default definePlugin({
9 name: "t3code",10 name: "t3code",
10 },11 },
11 rules: {12 rules: {
13 "namespace-node-imports": namespaceNodeImports,
12 "no-global-process-runtime": noGlobalProcessRuntime,14 "no-global-process-runtime": noGlobalProcessRuntime,
13 "no-inline-schema-compile": noInlineSchemaCompile,15 "no-inline-schema-compile": noInlineSchemaCompile,
14 "no-manual-effect-runtime-in-tests": noManualEffectRuntimeInTests,16 "no-manual-effect-runtime-in-tests": noManualEffectRuntimeInTests,
oxlint-plugin-t3code/rules/namespace-node-imports.test.ts created+63
...@@ -0,0 +1,63 @@
1import { assert, describe } from "@effect/vitest";
2
3import { createOxlintRuleHarness } from "../test/utils.ts";
4
5const rule = createOxlintRuleHarness("t3code/namespace-node-imports");
6
7describe("t3code/namespace-node-imports", () => {
8 rule.valid(
9 "allows canonical Node namespaces",
10 `
11 import * as NodeFS from "node:fs";
12 import * as NodeFSP from "node:fs/promises";
13 import * as NodeAssert from "node:assert/strict";
14 import * as NodeChildProcess from "node:child_process";
15 import * as NodeTimersPromises from "node:timers/promises";
16 import type * as NodeStream from "node:stream";
17
18 NodeAssert.ok(NodeChildProcess.spawn && NodeTimersPromises.setTimeout);
19 export const read = NodeFS.readFileSync;
20 export const readAsync = NodeFSP.readFile;
21 export type Input = NodeStream.Readable;
22 `,
23 );
24
25 rule.valid(
26 "does not apply to non-Node packages",
27 `
28 import { BrowserWindow } from "electron";
29 `,
30 );
31
32 rule.invalid(
33 "reports named imports",
34 `
35 import { readFile } from "node:fs/promises";
36 `,
37 (output) => {
38 assert.match(output, /namespace named NodeFSP/);
39 },
40 );
41
42 rule.invalid(
43 "reports default imports",
44 `
45 import path from "node:path";
46 `,
47 (output) => {
48 assert.match(output, /namespace named NodePath/);
49 },
50 );
51
52 rule.invalid(
53 "reports non-canonical namespace aliases",
54 `
55 import * as Crypto from "node:crypto";
56 import * as NodeOs from "node:os";
57 `,
58 (output) => {
59 assert.match(output, /namespace named NodeCrypto/);
60 assert.match(output, /namespace named NodeOS/);
61 },
62 );
63});
oxlint-plugin-t3code/rules/namespace-node-imports.ts created+76
...@@ -0,0 +1,76 @@
1import { defineRule } from "@oxlint/plugins";
2
3const NODE_MODULE_ALIASES = new Map([
4 ["assert/strict", "Assert"],
5 ["fs/promises", "FSP"],
6]);
7
8const NODE_SEGMENT_ALIASES = new Map([
9 ["fs", "FS"],
10 ["os", "OS"],
11 ["url", "URL"],
12 ["vm", "VM"],
13]);
14
15const toPascalCase = (value: string) =>
16 value
17 .split(/[_-]/u)
18 .filter((segment) => segment.length > 0)
19 .map((segment) => segment[0]?.toUpperCase() + segment.slice(1))
20 .join("");
21
22const expectedNamespaceAlias = (source: string) => {
23 const moduleName = source.slice("node:".length);
24 const knownAlias = NODE_MODULE_ALIASES.get(moduleName);
25 if (knownAlias !== undefined) return `Node${knownAlias}`;
26
27 return `Node${moduleName
28 .split("/")
29 .map((segment) => NODE_SEGMENT_ALIASES.get(segment) ?? toPascalCase(segment))
30 .join("")}`;
31};
32
33const literalStringValue = (node: unknown): string | undefined => {
34 if (typeof node !== "object" || node === null) return undefined;
35 if (!("type" in node) || node.type !== "Literal") return undefined;
36 if (!("value" in node) || typeof node.value !== "string") return undefined;
37 return node.value;
38};
39
40const identifierName = (node: unknown): string | undefined => {
41 if (typeof node !== "object" || node === null) return undefined;
42 if (!("type" in node) || node.type !== "Identifier") return undefined;
43 if (!("name" in node) || typeof node.name !== "string") return undefined;
44 return node.name;
45};
46
47export default defineRule({
48 meta: {
49 type: "problem",
50 docs: {
51 description: "Require canonical namespace imports for Node.js built-in modules.",
52 },
53 },
54 create(context) {
55 return {
56 ImportDeclaration(node) {
57 const source = literalStringValue(node.source);
58 if (source === undefined || !source.startsWith("node:")) return;
59
60 const expectedAlias = expectedNamespaceAlias(source);
61 const namespaceImport =
62 node.specifiers.length === 1 && node.specifiers[0]?.type === "ImportNamespaceSpecifier"
63 ? node.specifiers[0]
64 : undefined;
65 const actualAlias = identifierName(namespaceImport?.local);
66
67 if (actualAlias === expectedAlias) return;
68
69 context.report({
70 node,
71 message: `Import ${source} as a namespace named ${expectedAlias}.`,
72 });
73 },
74 };
75 },
76});
packages/shared/src/logging.ts+15-15
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import fs from "node:fs";2import * as NodeFS from "node:fs";
3import path from "node:path";3import * as NodePath from "node:path";
44
5export interface RotatingFileSinkOptions {5export interface RotatingFileSinkOptions {
6 readonly filePath: string;6 readonly filePath: string;
...@@ -29,7 +29,7 @@ export class RotatingFileSink {...@@ -29,7 +29,7 @@ export class RotatingFileSink {
29 this.maxFiles = options.maxFiles;29 this.maxFiles = options.maxFiles;
30 this.throwOnError = options.throwOnError ?? false;30 this.throwOnError = options.throwOnError ?? false;
3131
32 fs.mkdirSync(path.dirname(this.filePath), { recursive: true });32 NodeFS.mkdirSync(NodePath.dirname(this.filePath), { recursive: true });
33 this.pruneOverflowBackups();33 this.pruneOverflowBackups();
34 this.currentSize = this.readCurrentSize();34 this.currentSize = this.readCurrentSize();
35 }35 }
...@@ -43,7 +43,7 @@ export class RotatingFileSink {...@@ -43,7 +43,7 @@ export class RotatingFileSink {
43 this.rotate();43 this.rotate();
44 }44 }
4545
46 fs.appendFileSync(this.filePath, buffer);46 NodeFS.appendFileSync(this.filePath, buffer);
47 this.currentSize += buffer.length;47 this.currentSize += buffer.length;
4848
49 if (this.currentSize > this.maxBytes) {49 if (this.currentSize > this.maxBytes) {
...@@ -60,20 +60,20 @@ export class RotatingFileSink {...@@ -60,20 +60,20 @@ export class RotatingFileSink {
60 private rotate(): void {60 private rotate(): void {
61 try {61 try {
62 const oldest = this.withSuffix(this.maxFiles);62 const oldest = this.withSuffix(this.maxFiles);
63 if (fs.existsSync(oldest)) {63 if (NodeFS.existsSync(oldest)) {
64 fs.rmSync(oldest, { force: true });64 NodeFS.rmSync(oldest, { force: true });
65 }65 }
6666
67 for (let index = this.maxFiles - 1; index >= 1; index -= 1) {67 for (let index = this.maxFiles - 1; index >= 1; index -= 1) {
68 const source = this.withSuffix(index);68 const source = this.withSuffix(index);
69 const target = this.withSuffix(index + 1);69 const target = this.withSuffix(index + 1);
70 if (fs.existsSync(source)) {70 if (NodeFS.existsSync(source)) {
71 fs.renameSync(source, target);71 NodeFS.renameSync(source, target);
72 }72 }
73 }73 }
7474
75 if (fs.existsSync(this.filePath)) {75 if (NodeFS.existsSync(this.filePath)) {
76 fs.renameSync(this.filePath, this.withSuffix(1));76 NodeFS.renameSync(this.filePath, this.withSuffix(1));
77 }77 }
7878
79 this.currentSize = 0;79 this.currentSize = 0;
...@@ -87,13 +87,13 @@ export class RotatingFileSink {...@@ -87,13 +87,13 @@ export class RotatingFileSink {
8787
88 private pruneOverflowBackups(): void {88 private pruneOverflowBackups(): void {
89 try {89 try {
90 const dir = path.dirname(this.filePath);90 const dir = NodePath.dirname(this.filePath);
91 const baseName = path.basename(this.filePath);91 const baseName = NodePath.basename(this.filePath);
92 for (const entry of fs.readdirSync(dir)) {92 for (const entry of NodeFS.readdirSync(dir)) {
93 if (!entry.startsWith(`${baseName}.`)) continue;93 if (!entry.startsWith(`${baseName}.`)) continue;
94 const suffix = Number(entry.slice(baseName.length + 1));94 const suffix = Number(entry.slice(baseName.length + 1));
95 if (!Number.isInteger(suffix) || suffix <= this.maxFiles) continue;95 if (!Number.isInteger(suffix) || suffix <= this.maxFiles) continue;
96 fs.rmSync(path.join(dir, entry), { force: true });96 NodeFS.rmSync(NodePath.join(dir, entry), { force: true });
97 }97 }
98 } catch {98 } catch {
99 if (this.throwOnError) {99 if (this.throwOnError) {
...@@ -104,7 +104,7 @@ export class RotatingFileSink {...@@ -104,7 +104,7 @@ export class RotatingFileSink {
104104
105 private readCurrentSize(): number {105 private readCurrentSize(): number {
106 try {106 try {
107 return fs.statSync(this.filePath).size;107 return NodeFS.statSync(this.filePath).size;
108 } catch {108 } catch {
109 return 0;109 return 0;
110 }110 }
packages/shared/src/shell.ts+8-8
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import * as NodeOS from "node:os";2import * as NodeOS from "node:os";
3import * as NodePath from "node:path";3import * as NodePath from "node:path";
4import { execFileSync } from "node:child_process";4import * as NodeChildProcess from "node:child_process";
5import { accessSync, constants as fileSystemConstants, statSync } from "node:fs";5import * as NodeFS from "node:fs";
6import * as Data from "effect/Data";6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";7import * as Effect from "effect/Effect";
8import * as FileSystem from "effect/FileSystem";8import * as FileSystem from "effect/FileSystem";
...@@ -26,7 +26,7 @@ type ExecFileSyncLike = (...@@ -26,7 +26,7 @@ type ExecFileSyncLike = (
2626
27function canExecuteFile(filePath: string): boolean {27function canExecuteFile(filePath: string): boolean {
28 try {28 try {
29 accessSync(filePath, fileSystemConstants.X_OK);29 NodeFS.accessSync(filePath, NodeFS.constants.X_OK);
30 return true;30 return true;
31 } catch {31 } catch {
32 return false;32 return false;
...@@ -108,7 +108,7 @@ function resolveSpawnExecutableWithNode(...@@ -108,7 +108,7 @@ function resolveSpawnExecutableWithNode(
108 );108 );
109 const isExecutable = (candidate: string) => {109 const isExecutable = (candidate: string) => {
110 try {110 try {
111 if (!statSync(candidate).isFile()) return false;111 if (!NodeFS.statSync(candidate).isFile()) return false;
112 if (platform === "win32") {112 if (platform === "win32") {
113 return windowsPathExtensions.includes(path.extname(candidate).toUpperCase());113 return windowsPathExtensions.includes(path.extname(candidate).toUpperCase());
114 }114 }
...@@ -192,13 +192,13 @@ export function extractPathFromShellOutput(output: string): string | null {...@@ -192,13 +192,13 @@ export function extractPathFromShellOutput(output: string): string | null {
192192
193export function readPathFromLoginShell(193export function readPathFromLoginShell(
194 shell: string,194 shell: string,
195 execFile: ExecFileSyncLike = execFileSync,195 execFile: ExecFileSyncLike = NodeChildProcess.execFileSync,
196): string | undefined {196): string | undefined {
197 return readEnvironmentFromLoginShell(shell, ["PATH"], execFile).PATH;197 return readEnvironmentFromLoginShell(shell, ["PATH"], execFile).PATH;
198}198}
199199
200export function readPathFromLaunchctl(200export function readPathFromLaunchctl(
201 execFile: ExecFileSyncLike = execFileSync,201 execFile: ExecFileSyncLike = NodeChildProcess.execFileSync,
202): string | undefined {202): string | undefined {
203 try {203 try {
204 return trimNonEmpty(204 return trimNonEmpty(
...@@ -305,7 +305,7 @@ export type ShellEnvironmentReader = (...@@ -305,7 +305,7 @@ export type ShellEnvironmentReader = (
305export const readEnvironmentFromLoginShell: ShellEnvironmentReader = (305export const readEnvironmentFromLoginShell: ShellEnvironmentReader = (
306 shell,306 shell,
307 names,307 names,
308 execFile = execFileSync,308 execFile = NodeChildProcess.execFileSync,
309) => {309) => {
310 if (names.length === 0) {310 if (names.length === 0) {
311 return {};311 return {};
...@@ -371,7 +371,7 @@ export function readEnvironmentFromWindowsShell(...@@ -371,7 +371,7 @@ export function readEnvironmentFromWindowsShell(
371 const execFile: ExecFileSyncLike =371 const execFile: ExecFileSyncLike =
372 typeof optionsOrExecFile === "function"372 typeof optionsOrExecFile === "function"
373 ? optionsOrExecFile373 ? optionsOrExecFile
374 : (maybeExecFile ?? (execFileSync as ExecFileSyncLike));374 : (maybeExecFile ?? (NodeChildProcess.execFileSync as ExecFileSyncLike));
375 const command = buildWindowsEnvironmentCaptureCommand(names);375 const command = buildWindowsEnvironmentCaptureCommand(names);
376 const args = [376 const args = [
377 "-NoLogo",377 "-NoLogo",
packages/ssh/src/command.ts+5-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import * as Crypto from "node:crypto";1import * as NodeCrypto from "node:crypto";
22
3import type { DesktopSshEnvironmentTarget, DesktopUpdateChannel } from "@t3tools/contracts";3import type { DesktopSshEnvironmentTarget, DesktopUpdateChannel } from "@t3tools/contracts";
4import { HostProcessPlatform } from "@t3tools/shared/hostProcess";4import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
...@@ -74,7 +74,10 @@ export function targetConnectionKey(target: DesktopSshEnvironmentTarget): string...@@ -74,7 +74,10 @@ export function targetConnectionKey(target: DesktopSshEnvironmentTarget): string
74}74}
7575
76export function remoteStateKey(target: DesktopSshEnvironmentTarget): string {76export function remoteStateKey(target: DesktopSshEnvironmentTarget): string {
77 return Crypto.createHash("sha256").update(targetConnectionKey(target)).digest("hex").slice(0, 16);77 return NodeCrypto.createHash("sha256")
78 .update(targetConnectionKey(target))
79 .digest("hex")
80 .slice(0, 16);
78}81}
7982
80export function buildSshHostSpec(target: DesktopSshEnvironmentTarget): string {83export function buildSshHostSpec(target: DesktopSshEnvironmentTarget): string {
scripts/build-desktop-artifact.ts+2-2
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1#!/usr/bin/env node1#!/usr/bin/env node
22
3import { createRequire } from "node:module";3import * as NodeModule from "node:module";
44
5import { fromYaml } from "@t3tools/shared/schemaYaml";5import { fromYaml } from "@t3tools/shared/schemaYaml";
6import { HostProcessPlatform } from "@t3tools/shared/hostProcess";6import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
...@@ -482,7 +482,7 @@ const stageClerkPasskeyNativeBinaries = Effect.fn("stageClerkPasskeyNativeBinari...@@ -482,7 +482,7 @@ const stageClerkPasskeyNativeBinaries = Effect.fn("stageClerkPasskeyNativeBinari
482 path.join(stageAppDir, "node_modules", "@clerk", "electron-passkeys", "index.js"),482 path.join(stageAppDir, "node_modules", "@clerk", "electron-passkeys", "index.js"),
483 );483 );
484 const packageDir = path.dirname(packageEntryPath);484 const packageDir = path.dirname(packageEntryPath);
485 const packageRequire = createRequire(packageEntryPath);485 const packageRequire = NodeModule.createRequire(packageEntryPath);
486486
487 for (const artifact of resolveClerkPasskeyNativeArtifacts(platform, arch)) {487 for (const artifact of resolveClerkPasskeyNativeArtifacts(platform, arch)) {
488 const sourcePath = yield* Effect.try({488 const sourcePath = yield* Effect.try({
scripts/lib/public-config.test.ts+9-9
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off - Tests exercise root env file precedence directly.1// @effect-diagnostics nodeBuiltinImport:off - Tests exercise root env file precedence directly.
2import { mkdtempSync, rmSync, writeFileSync } from "node:fs";2import * as NodeFS from "node:fs";
3import { tmpdir } from "node:os";3import * as NodeOS from "node:os";
4import { join } from "node:path";4import * as NodePath from "node:path";
5import { afterEach, describe, expect, it } from "vite-plus/test";5import { afterEach, describe, expect, it } from "vite-plus/test";
66
7import { loadRepoEnv, resolvePublicConfig } from "./public-config.ts";7import { loadRepoEnv, resolvePublicConfig } from "./public-config.ts";
...@@ -10,7 +10,7 @@ const temporaryDirectories: string[] = [];...@@ -10,7 +10,7 @@ const temporaryDirectories: string[] = [];
1010
11afterEach(() => {11afterEach(() => {
12 for (const directory of temporaryDirectories.splice(0)) {12 for (const directory of temporaryDirectories.splice(0)) {
13 rmSync(directory, { recursive: true, force: true });13 NodeFS.rmSync(directory, { recursive: true, force: true });
14 }14 }
15});15});
1616
...@@ -43,12 +43,12 @@ describe("loadRepoEnv", () => {...@@ -43,12 +43,12 @@ describe("loadRepoEnv", () => {
4343
44 it("applies process, root local, and root precedence in that order", () => {44 it("applies process, root local, and root precedence in that order", () => {
45 const repoRoot = makeTemporaryDirectory();45 const repoRoot = makeTemporaryDirectory();
46 writeFileSync(46 NodeFS.writeFileSync(
47 join(repoRoot, ".env"),47 NodePath.join(repoRoot, ".env"),
48 "T3CODE_CLERK_PUBLISHABLE_KEY=pk_root\nT3CODE_CLERK_JWT_TEMPLATE=template_root\nT3CODE_CLERK_CLI_OAUTH_CLIENT_ID=oauth_root\nT3CODE_RELAY_URL=https://root.example.test\n",48 "T3CODE_CLERK_PUBLISHABLE_KEY=pk_root\nT3CODE_CLERK_JWT_TEMPLATE=template_root\nT3CODE_CLERK_CLI_OAUTH_CLIENT_ID=oauth_root\nT3CODE_RELAY_URL=https://root.example.test\n",
49 );49 );
50 writeFileSync(50 NodeFS.writeFileSync(
51 join(repoRoot, ".env.local"),51 NodePath.join(repoRoot, ".env.local"),
52 "T3CODE_CLERK_PUBLISHABLE_KEY=pk_local\nT3CODE_CLERK_JWT_TEMPLATE=template_local\nT3CODE_CLERK_CLI_OAUTH_CLIENT_ID=oauth_local\nT3CODE_RELAY_URL=https://local.example.test\n",52 "T3CODE_CLERK_PUBLISHABLE_KEY=pk_local\nT3CODE_CLERK_JWT_TEMPLATE=template_local\nT3CODE_CLERK_CLI_OAUTH_CLIENT_ID=oauth_local\nT3CODE_RELAY_URL=https://local.example.test\n",
53 );53 );
5454
...@@ -148,7 +148,7 @@ describe("loadRepoEnv", () => {...@@ -148,7 +148,7 @@ describe("loadRepoEnv", () => {
148});148});
149149
150function makeTemporaryDirectory() {150function makeTemporaryDirectory() {
151 const directory = mkdtempSync(join(tmpdir(), "t3code-public-config-"));151 const directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3code-public-config-"));
152 temporaryDirectories.push(directory);152 temporaryDirectories.push(directory);
153 return directory;153 return directory;
154}154}
scripts/release-smoke.ts+62-64
...@@ -1,21 +1,13 @@...@@ -1,21 +1,13 @@
1// @effect-diagnostics nodeBuiltinImport:off1// @effect-diagnostics nodeBuiltinImport:off
2import { execFileSync } from "node:child_process";2import * as NodeChildProcess from "node:child_process";
3import {3import * as NodeFS from "node:fs";
4 cpSync,4import * as NodeOS from "node:os";
5 existsSync,5import * as NodePath from "node:path";
6 mkdirSync,6import * as NodeURL from "node:url";
7 mkdtempSync,
8 readFileSync,
9 rmSync,
10 writeFileSync,
11} from "node:fs";
12import { tmpdir } from "node:os";
13import { dirname, join, resolve } from "node:path";
14import { fileURLToPath } from "node:url";
15import * as Console from "effect/Console";7import * as Console from "effect/Console";
16import * as Effect from "effect/Effect";8import * as Effect from "effect/Effect";
179
18const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");10const repoRoot = NodePath.resolve(NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)), "..");
1911
20const workspaceFiles = [12const workspaceFiles = [
21 "package.json",13 "package.json",
...@@ -44,26 +36,26 @@ const workspaceFiles = [...@@ -44,26 +36,26 @@ const workspaceFiles = [
4436
45function copyWorkspaceManifestFixture(targetRoot: string): void {37function copyWorkspaceManifestFixture(targetRoot: string): void {
46 for (const relativePath of workspaceFiles) {38 for (const relativePath of workspaceFiles) {
47 const sourcePath = resolve(repoRoot, relativePath);39 const sourcePath = NodePath.resolve(repoRoot, relativePath);
48 const destinationPath = resolve(targetRoot, relativePath);40 const destinationPath = NodePath.resolve(targetRoot, relativePath);
49 mkdirSync(dirname(destinationPath), { recursive: true });41 NodeFS.mkdirSync(NodePath.dirname(destinationPath), { recursive: true });
50 cpSync(sourcePath, destinationPath);42 NodeFS.cpSync(sourcePath, destinationPath);
51 }43 }
5244
53 const patchesDirectory = resolve(repoRoot, "patches");45 const patchesDirectory = NodePath.resolve(repoRoot, "patches");
54 if (existsSync(patchesDirectory)) {46 if (NodeFS.existsSync(patchesDirectory)) {
55 cpSync(patchesDirectory, resolve(targetRoot, "patches"), { recursive: true });47 NodeFS.cpSync(patchesDirectory, NodePath.resolve(targetRoot, "patches"), { recursive: true });
56 }48 }
57}49}
5850
59function writeMacManifestFixtures(targetRoot: string): { arm64Path: string; x64Path: string } {51function writeMacManifestFixtures(targetRoot: string): { arm64Path: string; x64Path: string } {
60 const assetDirectory = resolve(targetRoot, "release-assets");52 const assetDirectory = NodePath.resolve(targetRoot, "release-assets");
61 mkdirSync(assetDirectory, { recursive: true });53 NodeFS.mkdirSync(assetDirectory, { recursive: true });
6254
63 const arm64Path = resolve(assetDirectory, "latest-mac.yml");55 const arm64Path = NodePath.resolve(assetDirectory, "latest-mac.yml");
64 const x64Path = resolve(assetDirectory, "latest-mac-x64.yml");56 const x64Path = NodePath.resolve(assetDirectory, "latest-mac-x64.yml");
6557
66 writeFileSync(58 NodeFS.writeFileSync(
67 arm64Path,59 arm64Path,
68 `version: 9.9.9-smoke.060 `version: 9.9.9-smoke.0
69files:61files:
...@@ -79,7 +71,7 @@ releaseDate: '2026-03-08T10:32:14.587Z'...@@ -79,7 +71,7 @@ releaseDate: '2026-03-08T10:32:14.587Z'
79`,71`,
80 );72 );
8173
82 writeFileSync(74 NodeFS.writeFileSync(
83 x64Path,75 x64Path,
84 `version: 9.9.9-smoke.076 `version: 9.9.9-smoke.0
85files:77files:
...@@ -102,13 +94,13 @@ function writeWindowsManifestFixtures(...@@ -102,13 +94,13 @@ function writeWindowsManifestFixtures(
102 targetRoot: string,94 targetRoot: string,
103 channel: string,95 channel: string,
104): { arm64Path: string; x64Path: string } {96): { arm64Path: string; x64Path: string } {
105 const assetDirectory = resolve(targetRoot, "release-assets");97 const assetDirectory = NodePath.resolve(targetRoot, "release-assets");
106 mkdirSync(assetDirectory, { recursive: true });98 NodeFS.mkdirSync(assetDirectory, { recursive: true });
10799
108 const arm64Path = resolve(assetDirectory, `${channel}-win-arm64.yml`);100 const arm64Path = NodePath.resolve(assetDirectory, `${channel}-win-arm64.yml`);
109 const x64Path = resolve(assetDirectory, `${channel}-win-x64.yml`);101 const x64Path = NodePath.resolve(assetDirectory, `${channel}-win-x64.yml`);
110102
111 writeFileSync(103 NodeFS.writeFileSync(
112 arm64Path,104 arm64Path,
113 `version: 9.9.9-smoke.0105 `version: 9.9.9-smoke.0
114files:106files:
...@@ -124,7 +116,7 @@ releaseDate: '2026-03-08T10:32:14.587Z'...@@ -124,7 +116,7 @@ releaseDate: '2026-03-08T10:32:14.587Z'
124`,116`,
125 );117 );
126118
127 writeFileSync(119 NodeFS.writeFileSync(
128 x64Path,120 x64Path,
129 `version: 9.9.9-smoke.0121 `version: 9.9.9-smoke.0
130files:122files:
...@@ -147,11 +139,11 @@ function writeWindowsBuilderDebugFixtures(targetRoot: string): {...@@ -147,11 +139,11 @@ function writeWindowsBuilderDebugFixtures(targetRoot: string): {
147 arm64Path: string;139 arm64Path: string;
148 x64Path: string;140 x64Path: string;
149} {141} {
150 const assetDirectory = resolve(targetRoot, "release-assets");142 const assetDirectory = NodePath.resolve(targetRoot, "release-assets");
151 mkdirSync(assetDirectory, { recursive: true });143 NodeFS.mkdirSync(assetDirectory, { recursive: true });
152144
153 const arm64Path = resolve(assetDirectory, "builder-debug-win-arm64.yml");145 const arm64Path = NodePath.resolve(assetDirectory, "builder-debug-win-arm64.yml");
154 const x64Path = resolve(assetDirectory, "builder-debug-win-x64.yml");146 const x64Path = NodePath.resolve(assetDirectory, "builder-debug-win-x64.yml");
155 const debugFixture = `arm64:147 const debugFixture = `arm64:
156 firstOrDefaultFilePatterns:148 firstOrDefaultFilePatterns:
157 - '**/*'149 - '**/*'
...@@ -160,8 +152,8 @@ nsis:...@@ -160,8 +152,8 @@ nsis:
160 !include "example.nsh"152 !include "example.nsh"
161`;153`;
162154
163 writeFileSync(arm64Path, debugFixture);155 NodeFS.writeFileSync(arm64Path, debugFixture);
164 writeFileSync(x64Path, debugFixture);156 NodeFS.writeFileSync(x64Path, debugFixture);
165157
166 return { arm64Path, x64Path };158 return { arm64Path, x64Path };
167}159}
...@@ -172,13 +164,13 @@ function assertContains(haystack: string, needle: string, message: string): void...@@ -172,13 +164,13 @@ function assertContains(haystack: string, needle: string, message: string): void
172}164}
173165
174function assertExists(path: string, message: string): void {166function assertExists(path: string, message: string): void {
175 if (!existsSync(path)) {167 if (!NodeFS.existsSync(path)) {
176 throw new Error(message);168 throw new Error(message);
177 }169 }
178}170}
179171
180function assertPackageVersion(path: string, version: string): void {172function assertPackageVersion(path: string, version: string): void {
181 const packageJson = JSON.parse(readFileSync(path, "utf8")) as {173 const packageJson = JSON.parse(NodeFS.readFileSync(path, "utf8")) as {
182 readonly version?: unknown;174 readonly version?: unknown;
183 };175 };
184176
...@@ -188,20 +180,20 @@ function assertPackageVersion(path: string, version: string): void {...@@ -188,20 +180,20 @@ function assertPackageVersion(path: string, version: string): void {
188}180}
189181
190function assertMissing(path: string, message: string): void {182function assertMissing(path: string, message: string): void {
191 if (existsSync(path)) {183 if (NodeFS.existsSync(path)) {
192 throw new Error(message);184 throw new Error(message);
193 }185 }
194}186}
195187
196const tempRoot = mkdtempSync(join(tmpdir(), "t3-release-smoke-"));188const tempRoot = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-release-smoke-"));
197189
198try {190try {
199 copyWorkspaceManifestFixture(tempRoot);191 copyWorkspaceManifestFixture(tempRoot);
200192
201 execFileSync(193 NodeChildProcess.execFileSync(
202 process.execPath,194 process.execPath,
203 [195 [
204 resolve(repoRoot, "scripts/update-release-package-versions.ts"),196 NodePath.resolve(repoRoot, "scripts/update-release-package-versions.ts"),
205 "9.9.9-smoke.0",197 "9.9.9-smoke.0",
206 "--root",198 "--root",
207 tempRoot,199 tempRoot,
...@@ -212,14 +204,14 @@ try {...@@ -212,14 +204,14 @@ try {
212 },204 },
213 );205 );
214206
215 rmSync(resolve(tempRoot, "pnpm-lock.yaml"), { force: true });207 NodeFS.rmSync(NodePath.resolve(tempRoot, "pnpm-lock.yaml"), { force: true });
216208
217 execFileSync("vp", ["install", "--lockfile-only", "--ignore-scripts"], {209 NodeChildProcess.execFileSync("vp", ["install", "--lockfile-only", "--ignore-scripts"], {
218 cwd: tempRoot,210 cwd: tempRoot,
219 stdio: "inherit",211 stdio: "inherit",
220 });212 });
221213
222 const lockfile = readFileSync(resolve(tempRoot, "pnpm-lock.yaml"), "utf8");214 const lockfile = NodeFS.readFileSync(NodePath.resolve(tempRoot, "pnpm-lock.yaml"), "utf8");
223 assertContains(lockfile, "lockfileVersion:", "Expected pnpm-lock.yaml to be regenerated.");215 assertContains(lockfile, "lockfileVersion:", "Expected pnpm-lock.yaml to be regenerated.");
224216
225 for (const relativePath of [217 for (const relativePath of [
...@@ -228,13 +220,13 @@ try {...@@ -228,13 +220,13 @@ try {
228 "apps/web/package.json",220 "apps/web/package.json",
229 "packages/contracts/package.json",221 "packages/contracts/package.json",
230 ]) {222 ]) {
231 assertPackageVersion(resolve(tempRoot, relativePath), "9.9.9-smoke.0");223 assertPackageVersion(NodePath.resolve(tempRoot, relativePath), "9.9.9-smoke.0");
232 }224 }
233225
234 const nightlyReleaseMetadata = execFileSync(226 const nightlyReleaseMetadata = NodeChildProcess.execFileSync(
235 process.execPath,227 process.execPath,
236 [228 [
237 resolve(repoRoot, "scripts/resolve-nightly-release.ts"),229 NodePath.resolve(repoRoot, "scripts/resolve-nightly-release.ts"),
238 "--date",230 "--date",
239 "20260413",231 "20260413",
240 "--run-number",232 "--run-number",
...@@ -266,10 +258,10 @@ try {...@@ -266,10 +258,10 @@ try {
266 );258 );
267259
268 const { arm64Path, x64Path } = writeMacManifestFixtures(tempRoot);260 const { arm64Path, x64Path } = writeMacManifestFixtures(tempRoot);
269 execFileSync(261 NodeChildProcess.execFileSync(
270 process.execPath,262 process.execPath,
271 [263 [
272 resolve(repoRoot, "scripts/merge-update-manifests.ts"),264 NodePath.resolve(repoRoot, "scripts/merge-update-manifests.ts"),
273 "--platform",265 "--platform",
274 "mac",266 "mac",
275 arm64Path,267 arm64Path,
...@@ -281,7 +273,7 @@ try {...@@ -281,7 +273,7 @@ try {
281 },273 },
282 );274 );
283275
284 const mergedManifest = readFileSync(arm64Path, "utf8");276 const mergedManifest = NodeFS.readFileSync(arm64Path, "utf8");
285 assertContains(277 assertContains(
286 mergedManifest,278 mergedManifest,
287 "T3-Code-9.9.9-smoke.0-arm64.zip",279 "T3-Code-9.9.9-smoke.0-arm64.zip",
...@@ -297,21 +289,21 @@ try {...@@ -297,21 +289,21 @@ try {
297 tempRoot,289 tempRoot,
298 "latest",290 "latest",
299 );291 );
300 const mergedWindowsManifestPath = resolve(tempRoot, "release-assets/latest.yml");292 const mergedWindowsManifestPath = NodePath.resolve(tempRoot, "release-assets/latest.yml");
301 const { arm64Path: nightlyWinArm64Path, x64Path: nightlyWinX64Path } =293 const { arm64Path: nightlyWinArm64Path, x64Path: nightlyWinX64Path } =
302 writeWindowsManifestFixtures(tempRoot, "nightly");294 writeWindowsManifestFixtures(tempRoot, "nightly");
303 const mergedNightlyWindowsManifestPath = resolve(tempRoot, "release-assets/nightly.yml");295 const mergedNightlyWindowsManifestPath = NodePath.resolve(tempRoot, "release-assets/nightly.yml");
304 const { arm64Path: previewWinArm64Path, x64Path: previewWinX64Path } =296 const { arm64Path: previewWinArm64Path, x64Path: previewWinX64Path } =
305 writeWindowsManifestFixtures(tempRoot, "preview");297 writeWindowsManifestFixtures(tempRoot, "preview");
306 const mergedPreviewWindowsManifestPath = resolve(tempRoot, "release-assets/preview.yml");298 const mergedPreviewWindowsManifestPath = NodePath.resolve(tempRoot, "release-assets/preview.yml");
307 const { arm64Path: winDebugArm64Path, x64Path: winDebugX64Path } =299 const { arm64Path: winDebugArm64Path, x64Path: winDebugX64Path } =
308 writeWindowsBuilderDebugFixtures(tempRoot);300 writeWindowsBuilderDebugFixtures(tempRoot);
309 execFileSync(301 NodeChildProcess.execFileSync(
310 "bash",302 "bash",
311 [303 [
312 "-lc",304 "-lc",
313 `305 `
314 release_assets_dir=${JSON.stringify(resolve(tempRoot, "release-assets"))}306 release_assets_dir=${JSON.stringify(NodePath.resolve(tempRoot, "release-assets"))}
315 shopt -s nullglob307 shopt -s nullglob
316 found_windows_manifest=false308 found_windows_manifest=false
317 for x64_manifest in "$release_assets_dir"/*-win-x64.yml; do309 for x64_manifest in "$release_assets_dir"/*-win-x64.yml; do
...@@ -327,7 +319,7 @@ try {...@@ -327,7 +319,7 @@ try {
327 fi319 fi
328320
329 found_windows_manifest=true321 found_windows_manifest=true
330 ${JSON.stringify(process.execPath)} ${JSON.stringify(resolve(repoRoot, "scripts/merge-update-manifests.ts"))} --platform win \322 ${JSON.stringify(process.execPath)} ${JSON.stringify(NodePath.resolve(repoRoot, "scripts/merge-update-manifests.ts"))} --platform win \
331 "$arm64_manifest" \323 "$arm64_manifest" \
332 "$x64_manifest" \324 "$x64_manifest" \
333 "$output_manifest"325 "$output_manifest"
...@@ -346,7 +338,7 @@ try {...@@ -346,7 +338,7 @@ try {
346 },338 },
347 );339 );
348340
349 const mergedWindowsManifest = readFileSync(mergedWindowsManifestPath, "utf8");341 const mergedWindowsManifest = NodeFS.readFileSync(mergedWindowsManifestPath, "utf8");
350 assertContains(342 assertContains(
351 mergedWindowsManifest,343 mergedWindowsManifest,
352 "T3-Code-9.9.9-smoke.0-arm64.exe",344 "T3-Code-9.9.9-smoke.0-arm64.exe",
...@@ -357,7 +349,10 @@ try {...@@ -357,7 +349,10 @@ try {
357 "T3-Code-9.9.9-smoke.0-x64.exe",349 "T3-Code-9.9.9-smoke.0-x64.exe",
358 "Merged Windows manifest is missing the x64 asset.",350 "Merged Windows manifest is missing the x64 asset.",
359 );351 );
360 const mergedNightlyWindowsManifest = readFileSync(mergedNightlyWindowsManifestPath, "utf8");352 const mergedNightlyWindowsManifest = NodeFS.readFileSync(
353 mergedNightlyWindowsManifestPath,
354 "utf8",
355 );
361 assertContains(356 assertContains(
362 mergedNightlyWindowsManifest,357 mergedNightlyWindowsManifest,
363 "T3-Code-9.9.9-smoke.0-arm64.exe",358 "T3-Code-9.9.9-smoke.0-arm64.exe",
...@@ -368,7 +363,10 @@ try {...@@ -368,7 +363,10 @@ try {
368 "T3-Code-9.9.9-smoke.0-x64.exe",363 "T3-Code-9.9.9-smoke.0-x64.exe",
369 "Merged nightly Windows manifest is missing the x64 asset.",364 "Merged nightly Windows manifest is missing the x64 asset.",
370 );365 );
371 const mergedPreviewWindowsManifest = readFileSync(mergedPreviewWindowsManifestPath, "utf8");366 const mergedPreviewWindowsManifest = NodeFS.readFileSync(
367 mergedPreviewWindowsManifestPath,
368 "utf8",
369 );
372 assertContains(370 assertContains(
373 mergedPreviewWindowsManifest,371 mergedPreviewWindowsManifest,
374 "T3-Code-9.9.9-smoke.0-arm64.exe",372 "T3-Code-9.9.9-smoke.0-arm64.exe",
...@@ -411,5 +409,5 @@ try {...@@ -411,5 +409,5 @@ try {
411409
412 Effect.runSync(Console.log("Release smoke checks passed."));410 Effect.runSync(Console.log("Release smoke checks passed."));
413} finally {411} finally {
414 rmSync(tempRoot, { recursive: true, force: true });412 NodeFS.rmSync(tempRoot, { recursive: true, force: true });
415}413}
vite.config.ts+3-2
...@@ -1,11 +1,11 @@...@@ -1,11 +1,11 @@
1import "vite-plus/test/config";1import "vite-plus/test/config";
2import { defineConfig } from "vite-plus";2import { defineConfig } from "vite-plus";
3import { fileURLToPath } from "node:url";3import * as NodeURL from "node:url";
44
5export default defineConfig({5export default defineConfig({
6 resolve: {6 resolve: {
7 alias: {7 alias: {
8 "~": fileURLToPath(new URL("./apps/web/src", import.meta.url)),8 "~": NodeURL.fileURLToPath(new URL("./apps/web/src", import.meta.url)),
9 },9 },
10 },10 },
11 test: {11 test: {
...@@ -111,6 +111,7 @@ export default defineConfig({...@@ -111,6 +111,7 @@ export default defineConfig({
111 "t3code/no-global-process-runtime": "error",111 "t3code/no-global-process-runtime": "error",
112 "t3code/no-inline-schema-compile": "warn",112 "t3code/no-inline-schema-compile": "warn",
113 "t3code/no-manual-effect-runtime-in-tests": "error",113 "t3code/no-manual-effect-runtime-in-tests": "error",
114 "t3code/namespace-node-imports": "error",
114 },115 },
115 options: {116 options: {
116 // Revisit once Oxlint's tsgolint path can integrate with @effect/tsgo diagnostics.117 // Revisit once Oxlint's tsgolint path can integrate with @effect/tsgo diagnostics.