/** * ServerConfig - Runtime configuration services. * * Defines process-level server configuration and networking helpers used by * startup and runtime layers. * * @module ServerConfig */import * as Context from "effect/Context";import * as Clock from "effect/Clock";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as LogLevel from "effect/LogLevel";import * as Path from "effect/Path";import * as Schema from "effect/Schema"; import { sweepStalePendingAttachments } from "./attachmentStore.ts"; export const DEFAULT_PORT = 3773; export const RuntimeMode = Schema.Literals(["web", "desktop"]);export type RuntimeMode = typeof RuntimeMode.Type; export const StartupPresentation = Schema.Literals(["browser", "headless"]);export type StartupPresentation = typeof StartupPresentation.Type; /** * ServerDerivedPaths - Derived paths from the base directory. */export interface ServerDerivedPaths { readonly stateDir: string; readonly dbPath: string; readonly keybindingsConfigPath: string; readonly settingsPath: string; /** Palettes this machine publishes for clients to follow, one file per theme. */ readonly environmentThemesDir: string; readonly providerStatusCacheDir: string; readonly worktreesDir: string; readonly attachmentsDir: string; /** Screenshots the agent asks the collaborative browser to keep for the user. */ readonly browserArtifactsDir: string; readonly logsDir: string; readonly serverLogPath: string; readonly serverTracePath: string; readonly providerLogsDir: string; readonly providerEventLogPath: string; readonly terminalLogsDir: string; readonly anonymousIdPath: string; readonly environmentIdPath: string; readonly serverRuntimeStatePath: string; readonly secretsDir: string;} export interface DeriveServerPathsOptions { readonly baseDirIsExplicit?: boolean;} /** * ServerConfig - Service tag for server runtime configuration. */export class ServerConfig extends Context.Service< ServerConfig, ServerDerivedPaths & { readonly logLevel: LogLevel.LogLevel; readonly traceMinLevel: LogLevel.LogLevel; readonly traceTimingEnabled: boolean; readonly traceBatchWindowMs: number; readonly traceMaxBytes: number; readonly traceMaxFiles: number; readonly otlpTracesUrl: string | undefined; readonly otlpMetricsUrl: string | undefined; readonly otlpExportIntervalMs: number; readonly otlpServiceName: string; readonly mode: RuntimeMode; readonly port: number; readonly host: string | undefined; readonly cwd: string; readonly baseDir: string; readonly staticDir: string | undefined; readonly devUrl: URL | undefined; readonly devAllowedOrigins: ReadonlyArray<string>; readonly noBrowser: boolean; readonly startupPresentation: StartupPresentation; readonly desktopBootstrapToken: string | undefined; readonly desktopTelemetryFd?: number | undefined; readonly desktopTelemetryControlFd?: number | undefined; readonly resourceMonitorPath?: string | undefined; readonly autoBootstrapProjectFromCwd: boolean; readonly logWebSocketEvents: boolean; readonly tailscaleServeEnabled: boolean; readonly tailscaleServePort: number; }>()("t3/config/ServerConfig") { /** @deprecated Import and use `layerTest` from this module. */ static readonly layerTest = ( cwd: string, baseDirOrPrefix: string | { readonly prefix: string }, ) => layerTest(cwd, baseDirOrPrefix);} export const make = (config: ServerConfig["Service"]) => ServerConfig.of(config); export const layer = (config: ServerConfig["Service"]) => Layer.succeed(ServerConfig, make(config)); export const deriveServerPaths = Effect.fn(function* ( baseDir: ServerConfig["Service"]["baseDir"], devUrl: ServerConfig["Service"]["devUrl"], options: DeriveServerPathsOptions = {},): Effect.fn.Return<ServerDerivedPaths, never, Path.Path> { const { join } = yield* Path.Path; const stateDir = join( baseDir, devUrl !== undefined && !options.baseDirIsExplicit ? "dev" : "userdata", ); const dbPath = join(stateDir, "state.sqlite"); const attachmentsDir = join(stateDir, "attachments"); const logsDir = join(stateDir, "logs"); const providerLogsDir = join(logsDir, "provider"); const providerStatusCacheDir = join(baseDir, "caches"); return { stateDir, dbPath, keybindingsConfigPath: join(stateDir, "keybindings.json"), settingsPath: join(stateDir, "settings.json"), environmentThemesDir: join(stateDir, "themes"), providerStatusCacheDir, worktreesDir: join(baseDir, "worktrees"), attachmentsDir, browserArtifactsDir: join(stateDir, "browser-artifacts"), logsDir, serverLogPath: join(logsDir, "server.log"), serverTracePath: join(logsDir, "server.trace.ndjson"), providerLogsDir, providerEventLogPath: join(providerLogsDir, "events.log"), terminalLogsDir: join(logsDir, "terminals"), anonymousIdPath: join(stateDir, "anonymous-id"), environmentIdPath: join(stateDir, "environment-id"), serverRuntimeStatePath: join(stateDir, "server-runtime.json"), secretsDir: join(stateDir, "secrets"), };}); export const ensureServerDirectories = Effect.fn(function* (derivedPaths: ServerDerivedPaths) { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* Effect.all( [ fs.makeDirectory(derivedPaths.stateDir, { recursive: true }), fs.makeDirectory(derivedPaths.logsDir, { recursive: true }), fs.makeDirectory(derivedPaths.providerLogsDir, { recursive: true }), fs.makeDirectory(derivedPaths.terminalLogsDir, { recursive: true }), fs.makeDirectory(derivedPaths.attachmentsDir, { recursive: true }), fs.makeDirectory(derivedPaths.worktreesDir, { recursive: true }), fs.makeDirectory(path.dirname(derivedPaths.keybindingsConfigPath), { recursive: true }), fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true }), fs.makeDirectory(derivedPaths.providerStatusCacheDir, { recursive: true }), fs.makeDirectory(path.dirname(derivedPaths.anonymousIdPath), { recursive: true }), fs.makeDirectory(path.dirname(derivedPaths.serverRuntimeStatePath), { recursive: true }), ], { concurrency: "unbounded" }, ); const swept = sweepStalePendingAttachments({ attachmentsDir: derivedPaths.attachmentsDir, nowMs: yield* Clock.currentTimeMillis, }); if (swept.deleted > 0) { yield* Effect.logInfo("Removed expired attachment uploads.", { deleted: swept.deleted }); }}); const makeTest = Effect.fn("ServerConfig.makeTest")(function* ( cwd: string, baseDirOrPrefix: string | { readonly prefix: string },) { const devUrl = undefined; const fs = yield* FileSystem.FileSystem; const baseDir = typeof baseDirOrPrefix === "string" ? baseDirOrPrefix : yield* fs.makeTempDirectoryScoped({ prefix: baseDirOrPrefix.prefix }); const derivedPaths = yield* deriveServerPaths(baseDir, devUrl); yield* ensureServerDirectories(derivedPaths); return ServerConfig.of({ logLevel: "Error", traceMinLevel: "Info", traceTimingEnabled: true, traceBatchWindowMs: 200, traceMaxBytes: 10 * 1024 * 1024, traceMaxFiles: 10, otlpTracesUrl: undefined, otlpMetricsUrl: undefined, otlpExportIntervalMs: 10_000, otlpServiceName: "t3-server", cwd, baseDir, ...derivedPaths, mode: "web", autoBootstrapProjectFromCwd: false, logWebSocketEvents: false, tailscaleServeEnabled: false, tailscaleServePort: 443, port: 0, host: undefined, desktopBootstrapToken: undefined, desktopTelemetryFd: undefined, desktopTelemetryControlFd: undefined, resourceMonitorPath: undefined, staticDir: undefined, devUrl, devAllowedOrigins: [], noBrowser: false, startupPresentation: "browser", });}); export const layerTest = (cwd: string, baseDirOrPrefix: string | { readonly prefix: string }) => Layer.effect(ServerConfig, makeTest(cwd, baseDirOrPrefix)); export const resolveStaticDir = Effect.fn(function* () { const { join, resolve } = yield* Path.Path; const { exists } = yield* FileSystem.FileSystem; const bundledClient = resolve(join(import.meta.dirname, "client")); const bundledStat = yield* exists(join(bundledClient, "index.html")).pipe( Effect.orElseSucceed(() => false), ); if (bundledStat) { return bundledClient; } const monorepoClient = resolve(join(import.meta.dirname, "../../web/dist")); const monorepoStat = yield* exists(join(monorepoClient, "index.html")).pipe( Effect.orElseSucceed(() => false), ); if (monorepoStat) { return monorepoClient; } return undefined;});
/**
* ServerConfig - Runtime configuration services.
*
* Defines process-level server configuration and networking helpers used by
* startup and runtime layers.
*
* @module ServerConfig
*/
import * as Context from "effect/Context";
import * as Clock from "effect/Clock";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as LogLevel from "effect/LogLevel";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import { sweepStalePendingAttachments } from "./attachmentStore.ts";
export const DEFAULT_PORT = 3773;
export const RuntimeMode = Schema.Literals(["web", "desktop"]);
export type RuntimeMode = typeof RuntimeMode.Type;
export const StartupPresentation = Schema.Literals(["browser", "headless"]);
export type StartupPresentation = typeof StartupPresentation.Type;
/**
* ServerDerivedPaths - Derived paths from the base directory.
*/
export interface ServerDerivedPaths {
readonly stateDir: string;
readonly dbPath: string;
readonly keybindingsConfigPath: string;
readonly settingsPath: string;
/** Palettes this machine publishes for clients to follow, one file per theme. */
readonly environmentThemesDir: string;
readonly providerStatusCacheDir: string;
readonly worktreesDir: string;
readonly attachmentsDir: string;
/** Screenshots the agent asks the collaborative browser to keep for the user. */
readonly browserArtifactsDir: string;
readonly logsDir: string;
readonly serverLogPath: string;
readonly serverTracePath: string;
readonly providerLogsDir: string;
readonly providerEventLogPath: string;
readonly terminalLogsDir: string;
readonly anonymousIdPath: string;
readonly environmentIdPath: string;
readonly serverRuntimeStatePath: string;
readonly secretsDir: string;
}
export interface DeriveServerPathsOptions {
readonly baseDirIsExplicit?: boolean;
}
/**
* ServerConfig - Service tag for server runtime configuration.
*/
export class ServerConfig extends Context.Service<
ServerConfig,
ServerDerivedPaths & {
readonly logLevel: LogLevel.LogLevel;
readonly traceMinLevel: LogLevel.LogLevel;
readonly traceTimingEnabled: boolean;
readonly traceBatchWindowMs: number;
readonly traceMaxBytes: number;
readonly traceMaxFiles: number;
readonly otlpTracesUrl: string | undefined;
readonly otlpMetricsUrl: string | undefined;
readonly otlpExportIntervalMs: number;
readonly otlpServiceName: string;
readonly mode: RuntimeMode;
readonly port: number;
readonly host: string | undefined;
readonly cwd: string;
readonly baseDir: string;
readonly staticDir: string | undefined;
readonly devUrl: URL | undefined;
readonly devAllowedOrigins: ReadonlyArray<string>;
readonly noBrowser: boolean;
readonly startupPresentation: StartupPresentation;
readonly desktopBootstrapToken: string | undefined;
readonly desktopTelemetryFd?: number | undefined;
readonly desktopTelemetryControlFd?: number | undefined;
readonly resourceMonitorPath?: string | undefined;
readonly autoBootstrapProjectFromCwd: boolean;
readonly logWebSocketEvents: boolean;
readonly tailscaleServeEnabled: boolean;
readonly tailscaleServePort: number;
}
>()("t3/config/ServerConfig") {
/** @deprecated Import and use `layerTest` from this module. */
static readonly layerTest = (
cwd: string,
baseDirOrPrefix: string | { readonly prefix: string },
) => layerTest(cwd, baseDirOrPrefix);
}
export const make = (config: ServerConfig["Service"]) => ServerConfig.of(config);
export const layer = (config: ServerConfig["Service"]) => Layer.succeed(ServerConfig, make(config));
export const deriveServerPaths = Effect.fn(function* (
baseDir: ServerConfig["Service"]["baseDir"],
devUrl: ServerConfig["Service"]["devUrl"],
options: DeriveServerPathsOptions = {},
): Effect.fn.Return<ServerDerivedPaths, never, Path.Path> {
const { join } = yield* Path.Path;
const stateDir = join(
baseDir,
devUrl !== undefined && !options.baseDirIsExplicit ? "dev" : "userdata",
);
const dbPath = join(stateDir, "state.sqlite");
const attachmentsDir = join(stateDir, "attachments");
const logsDir = join(stateDir, "logs");
const providerLogsDir = join(logsDir, "provider");
const providerStatusCacheDir = join(baseDir, "caches");
return {
stateDir,
dbPath,
keybindingsConfigPath: join(stateDir, "keybindings.json"),
settingsPath: join(stateDir, "settings.json"),
environmentThemesDir: join(stateDir, "themes"),
providerStatusCacheDir,
worktreesDir: join(baseDir, "worktrees"),
attachmentsDir,
browserArtifactsDir: join(stateDir, "browser-artifacts"),
logsDir,
serverLogPath: join(logsDir, "server.log"),
serverTracePath: join(logsDir, "server.trace.ndjson"),
providerLogsDir,
providerEventLogPath: join(providerLogsDir, "events.log"),
terminalLogsDir: join(logsDir, "terminals"),
anonymousIdPath: join(stateDir, "anonymous-id"),
environmentIdPath: join(stateDir, "environment-id"),
serverRuntimeStatePath: join(stateDir, "server-runtime.json"),
secretsDir: join(stateDir, "secrets"),
};
});
export const ensureServerDirectories = Effect.fn(function* (derivedPaths: ServerDerivedPaths) {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
yield* Effect.all(
[
fs.makeDirectory(derivedPaths.stateDir, { recursive: true }),
fs.makeDirectory(derivedPaths.logsDir, { recursive: true }),
fs.makeDirectory(derivedPaths.providerLogsDir, { recursive: true }),
fs.makeDirectory(derivedPaths.terminalLogsDir, { recursive: true }),
fs.makeDirectory(derivedPaths.attachmentsDir, { recursive: true }),
fs.makeDirectory(derivedPaths.worktreesDir, { recursive: true }),
fs.makeDirectory(path.dirname(derivedPaths.keybindingsConfigPath), { recursive: true }),
fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true }),
fs.makeDirectory(derivedPaths.providerStatusCacheDir, { recursive: true }),
fs.makeDirectory(path.dirname(derivedPaths.anonymousIdPath), { recursive: true }),
fs.makeDirectory(path.dirname(derivedPaths.serverRuntimeStatePath), { recursive: true }),
],
{ concurrency: "unbounded" },
);
const swept = sweepStalePendingAttachments({
attachmentsDir: derivedPaths.attachmentsDir,
nowMs: yield* Clock.currentTimeMillis,
});
if (swept.deleted > 0) {
yield* Effect.logInfo("Removed expired attachment uploads.", { deleted: swept.deleted });
}
});
const makeTest = Effect.fn("ServerConfig.makeTest")(function* (
cwd: string,
baseDirOrPrefix: string | { readonly prefix: string },
) {
const devUrl = undefined;
const fs = yield* FileSystem.FileSystem;
const baseDir =
typeof baseDirOrPrefix === "string"
? baseDirOrPrefix
: yield* fs.makeTempDirectoryScoped({ prefix: baseDirOrPrefix.prefix });
const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);
yield* ensureServerDirectories(derivedPaths);
return ServerConfig.of({
logLevel: "Error",
traceMinLevel: "Info",
traceTimingEnabled: true,
traceBatchWindowMs: 200,
traceMaxBytes: 10 * 1024 * 1024,
traceMaxFiles: 10,
otlpTracesUrl: undefined,
otlpMetricsUrl: undefined,
otlpExportIntervalMs: 10_000,
otlpServiceName: "t3-server",
cwd,
baseDir,
...derivedPaths,
mode: "web",
autoBootstrapProjectFromCwd: false,
logWebSocketEvents: false,
tailscaleServeEnabled: false,
tailscaleServePort: 443,
port: 0,
host: undefined,
desktopBootstrapToken: undefined,
desktopTelemetryFd: undefined,
desktopTelemetryControlFd: undefined,
resourceMonitorPath: undefined,
staticDir: undefined,
devUrl,
devAllowedOrigins: [],
noBrowser: false,
startupPresentation: "browser",
});
});
export const layerTest = (cwd: string, baseDirOrPrefix: string | { readonly prefix: string }) =>
Layer.effect(ServerConfig, makeTest(cwd, baseDirOrPrefix));
export const resolveStaticDir = Effect.fn(function* () {
const { join, resolve } = yield* Path.Path;
const { exists } = yield* FileSystem.FileSystem;
const bundledClient = resolve(join(import.meta.dirname, "client"));
const bundledStat = yield* exists(join(bundledClient, "index.html")).pipe(
Effect.orElseSucceed(() => false),
);
if (bundledStat) {
return bundledClient;
}
const monorepoClient = resolve(join(import.meta.dirname, "../../web/dist"));
const monorepoStat = yield* exists(join(monorepoClient, "index.html")).pipe(
Effect.orElseSucceed(() => false),
);
if (monorepoStat) {
return monorepoClient;
}
return undefined;
});