/** * Server - HTTP/WebSocket server service interface. * * Owns startup and shutdown lifecycle of the HTTP server, static asset serving, * and WebSocket request routing. * * @module Server */import http from "node:http"; import Mime from "@effect/platform-node/Mime";import { CommandId, DEFAULT_PROVIDER_INTERACTION_MODE, type ClientOrchestrationCommand, type OrchestrationCommand, ORCHESTRATION_WS_CHANNELS, ORCHESTRATION_WS_METHODS, PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, ProjectId, ThreadId, WS_CHANNELS, WS_METHODS, WebSocketRequest, type WsResponse as WsResponseMessage, WsResponse, type WsPushEnvelopeBase,} from "@t3tools/contracts";import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";import * as Socket from "effect/unstable/socket/Socket";import { Cause, Effect, Exit, FileSystem, Layer, Option, Path, Ref, Result, Schema, Scope, ServiceMap, Stream, Struct,} from "effect";import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { createLogger } from "./logger";import { GitManager } from "./git/Services/GitManager.ts";import { TerminalManager } from "./terminal/Services/Manager.ts";import { Keybindings } from "./keybindings";import { searchWorkspaceEntries } from "./workspaceEntries";import { OrchestrationEngineService } from "./orchestration/Services/OrchestrationEngine";import { ProjectionSnapshotQuery } from "./orchestration/Services/ProjectionSnapshotQuery";import { OrchestrationReactor } from "./orchestration/Services/OrchestrationReactor";import { ProviderService } from "./provider/Services/ProviderService";import { ProviderHealth } from "./provider/Services/ProviderHealth";import { CheckpointDiffQuery } from "./checkpointing/Services/CheckpointDiffQuery";import { clamp } from "effect/Number";import { Open, resolveAvailableEditors } from "./open";import { ServerConfig } from "./config";import { GitCore } from "./git/Services/GitCore.ts";import { handleProjectFaviconRequest } from "./projectFaviconRoute";import { ATTACHMENTS_ROUTE_PREFIX, normalizeAttachmentRelativePath, resolveAttachmentRelativePath,} from "./attachmentPaths"; import { createAttachmentId, resolveAttachmentPath, resolveAttachmentPathById,} from "./attachmentStore.ts";import { parseBase64DataUrl } from "./imageMime.ts";import { AnalyticsService } from "./telemetry/Services/AnalyticsService.ts";import { expandHomePath } from "./os-jank.ts";import { makeServerPushBus, type ServerPushClient } from "./wsServer/pushBus.ts";import { makeServerReadiness } from "./wsServer/readiness.ts";import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; /** * ServerShape - Service API for server lifecycle control. */export interface ServerShape { /** * Start HTTP and WebSocket listeners. */ readonly start: Effect.Effect< http.Server, ServerLifecycleError, Scope.Scope | ServerRuntimeServices | ServerConfig | FileSystem.FileSystem | Path.Path >; /** * Wait for process shutdown signals. */ readonly stopSignal: Effect.Effect<void, never>;} /** * Server - Service tag for HTTP/WebSocket lifecycle management. */export class Server extends ServiceMap.Service<Server, ServerShape>()("t3/wsServer/Server") {} function websocketRawToString(raw: string | Uint8Array): string { if (typeof raw === "string") { return raw; } return Buffer.from(raw).toString("utf8");} function toPosixRelativePath(input: string): string { return input.replaceAll("\\", "/");} function resolveWorkspaceWritePath(params: { workspaceRoot: string; relativePath: string; path: Path.Path;}): Effect.Effect<{ absolutePath: string; relativePath: string }, RouteRequestError> { const normalizedInputPath = params.relativePath.trim(); if (params.path.isAbsolute(normalizedInputPath)) { return Effect.fail( new RouteRequestError({ message: "Workspace file path must be relative to the project root.", }), ); } const absolutePath = params.path.resolve(params.workspaceRoot, normalizedInputPath); const relativeToRoot = toPosixRelativePath( params.path.relative(params.workspaceRoot, absolutePath), ); if ( relativeToRoot.length === 0 || relativeToRoot === "." || relativeToRoot.startsWith("../") || relativeToRoot === ".." || params.path.isAbsolute(relativeToRoot) ) { return Effect.fail( new RouteRequestError({ message: "Workspace file path must stay within the project root.", }), ); } return Effect.succeed({ absolutePath, relativePath: relativeToRoot, });} function stripRequestTag<T extends { _tag: string }>(body: T) { return Struct.omit(body, ["_tag"]);} const encodeWsResponse = Schema.encodeEffect(Schema.fromJsonString(WsResponse));const decodeWebSocketRequest = decodeJsonResult(WebSocketRequest); export type ServerCoreRuntimeServices = | OrchestrationEngineService | ProjectionSnapshotQuery | CheckpointDiffQuery | OrchestrationReactor | ProviderService | ProviderHealth; export type ServerRuntimeServices = | ServerCoreRuntimeServices | GitManager | GitCore | TerminalManager | Keybindings | Open | AnalyticsService; export class ServerLifecycleError extends Schema.TaggedErrorClass<ServerLifecycleError>()( "ServerLifecycleError", { operation: Schema.String, cause: Schema.optional(Schema.Defect), },) {} class RouteRequestError extends Schema.TaggedErrorClass<RouteRequestError>()("RouteRequestError", { message: Schema.String,}) {} export const createServer = Effect.fn(function* (): Effect.fn.Return< http.Server, ServerLifecycleError, Scope.Scope | ServerRuntimeServices | ServerConfig | FileSystem.FileSystem | Path.Path> { const serverConfig = yield* ServerConfig; const { port, cwd, keybindingsConfigPath, staticDir, devUrl, authToken, host, logWebSocketEvents, autoBootstrapProjectFromCwd, } = serverConfig; const availableEditors = resolveAvailableEditors(); const gitManager = yield* GitManager; const terminalManager = yield* TerminalManager; const keybindingsManager = yield* Keybindings; const providerHealth = yield* ProviderHealth; const git = yield* GitCore; const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* keybindingsManager.syncDefaultKeybindingsOnStartup.pipe( Effect.catch((error) => Effect.logWarning("failed to sync keybindings defaults on startup", { path: error.configPath, detail: error.detail, cause: error.cause, }), ), ); const providerStatuses = yield* providerHealth.getStatuses; const clients = yield* Ref.make(new Set<ServerPushClient>()); const logger = createLogger("ws"); const readiness = yield* makeServerReadiness; function logOutgoingPush(push: WsPushEnvelopeBase, recipients: number) { if (!logWebSocketEvents) return; logger.event("outgoing push", { channel: push.channel, sequence: push.sequence, recipients, payload: push.data, }); } const pushBus = yield* makeServerPushBus({ clients, logOutgoingPush, }); yield* readiness.markPushBusReady; yield* keybindingsManager.start.pipe( Effect.mapError( (cause) => new ServerLifecycleError({ operation: "keybindingsRuntimeStart", cause }), ), ); yield* readiness.markKeybindingsReady; const normalizeDispatchCommand = Effect.fnUntraced(function* (input: { readonly command: ClientOrchestrationCommand; }) { const normalizeProjectWorkspaceRoot = Effect.fnUntraced(function* (workspaceRoot: string) { const normalizedWorkspaceRoot = path.resolve(yield* expandHomePath(workspaceRoot.trim())); const workspaceStat = yield* fileSystem .stat(normalizedWorkspaceRoot) .pipe(Effect.catch(() => Effect.succeed(null))); if (!workspaceStat) { return yield* new RouteRequestError({ message: `Project directory does not exist: ${normalizedWorkspaceRoot}`, }); } if (workspaceStat.type !== "Directory") { return yield* new RouteRequestError({ message: `Project path is not a directory: ${normalizedWorkspaceRoot}`, }); } return normalizedWorkspaceRoot; }); if (input.command.type === "project.create") { return { ...input.command, workspaceRoot: yield* normalizeProjectWorkspaceRoot(input.command.workspaceRoot), } satisfies OrchestrationCommand; } if (input.command.type === "project.meta.update" && input.command.workspaceRoot !== undefined) { return { ...input.command, workspaceRoot: yield* normalizeProjectWorkspaceRoot(input.command.workspaceRoot), } satisfies OrchestrationCommand; } if (input.command.type !== "thread.turn.start") { return input.command as OrchestrationCommand; } const turnStartCommand = input.command; const normalizedAttachments = yield* Effect.forEach( turnStartCommand.message.attachments, (attachment) => Effect.gen(function* () { const parsed = parseBase64DataUrl(attachment.dataUrl); if (!parsed || !parsed.mimeType.startsWith("image/")) { return yield* new RouteRequestError({ message: `Invalid image attachment payload for '${attachment.name}'.`, }); } const bytes = Buffer.from(parsed.base64, "base64"); if (bytes.byteLength === 0 || bytes.byteLength > PROVIDER_SEND_TURN_MAX_IMAGE_BYTES) { return yield* new RouteRequestError({ message: `Image attachment '${attachment.name}' is empty or too large.`, }); } const attachmentId = createAttachmentId(turnStartCommand.threadId); if (!attachmentId) { return yield* new RouteRequestError({ message: "Failed to create a safe attachment id.", }); } const persistedAttachment = { type: "image" as const, id: attachmentId, name: attachment.name, mimeType: parsed.mimeType.toLowerCase(), sizeBytes: bytes.byteLength, }; const attachmentPath = resolveAttachmentPath({ stateDir: serverConfig.stateDir, attachment: persistedAttachment, }); if (!attachmentPath) { return yield* new RouteRequestError({ message: `Failed to resolve persisted path for '${attachment.name}'.`, }); } yield* fileSystem.makeDirectory(path.dirname(attachmentPath), { recursive: true }).pipe( Effect.mapError( () => new RouteRequestError({ message: `Failed to create attachment directory for '${attachment.name}'.`, }), ), ); yield* fileSystem.writeFile(attachmentPath, bytes).pipe( Effect.mapError( () => new RouteRequestError({ message: `Failed to persist attachment '${attachment.name}'.`, }), ), ); return persistedAttachment; }), { concurrency: 1 }, ); return { ...turnStartCommand, message: { ...turnStartCommand.message, attachments: normalizedAttachments, }, } satisfies OrchestrationCommand; }); const serveHttpRequest = Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const url = HttpServerRequest.toURL(request); if (!url) { return HttpServerResponse.text("Bad Request", { status: 400 }); } if (url.pathname === "/api/project-favicon") { return yield* handleProjectFaviconRequest(url.searchParams.get("cwd")); } if (url.pathname.startsWith(ATTACHMENTS_ROUTE_PREFIX)) { const rawRelativePath = url.pathname.slice(ATTACHMENTS_ROUTE_PREFIX.length); const normalizedRelativePath = normalizeAttachmentRelativePath(rawRelativePath); if (!normalizedRelativePath) { return HttpServerResponse.text("Invalid attachment path", { status: 400 }); } const isIdLookup = !normalizedRelativePath.includes("/") && !normalizedRelativePath.includes("."); const filePath = isIdLookup ? resolveAttachmentPathById({ stateDir: serverConfig.stateDir, attachmentId: normalizedRelativePath, }) : resolveAttachmentRelativePath({ stateDir: serverConfig.stateDir, relativePath: normalizedRelativePath, }); if (!filePath) { return HttpServerResponse.text(isIdLookup ? "Not Found" : "Invalid attachment path", { status: isIdLookup ? 404 : 400, }); } const fileInfo = yield* fileSystem .stat(filePath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!fileInfo || fileInfo.type !== "File") { return HttpServerResponse.text("Not Found", { status: 404 }); } const contentType = Mime.getType(filePath) ?? "application/octet-stream"; const data = yield* fileSystem .readFile(filePath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!data) { return HttpServerResponse.text("Internal Server Error", { status: 500 }); } return HttpServerResponse.uint8Array(data, { contentType, headers: { "Cache-Control": "public, max-age=31536000, immutable" }, }); } if (devUrl) { return HttpServerResponse.redirect(devUrl.href, { status: 302 }); } if (!staticDir) { return HttpServerResponse.text("No static directory configured and no dev URL set.", { status: 503, }); } const staticRoot = path.resolve(staticDir); const staticRequestPath = url.pathname === "/" ? "/index.html" : url.pathname; const rawStaticRelativePath = staticRequestPath.replace(/^[/\\]+/, ""); const hasRawLeadingParentSegment = rawStaticRelativePath.startsWith(".."); const staticRelativePath = path.normalize(rawStaticRelativePath).replace(/^[/\\]+/, ""); const hasPathTraversalSegment = staticRelativePath.startsWith(".."); if ( staticRelativePath.length === 0 || hasRawLeadingParentSegment || hasPathTraversalSegment || staticRelativePath.includes("\0") ) { return HttpServerResponse.text("Invalid static file path", { status: 400 }); } const isWithinStaticRoot = (candidate: string) => candidate === staticRoot || candidate.startsWith(staticRoot.endsWith(path.sep) ? staticRoot : `${staticRoot}${path.sep}`); let filePath = path.resolve(staticRoot, staticRelativePath); if (!isWithinStaticRoot(filePath)) { return HttpServerResponse.text("Invalid static file path", { status: 400 }); } const ext = path.extname(filePath); if (!ext) { filePath = path.resolve(filePath, "index.html"); if (!isWithinStaticRoot(filePath)) { return HttpServerResponse.text("Invalid static file path", { status: 400 }); } } const fileInfo = yield* fileSystem .stat(filePath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!fileInfo || fileInfo.type !== "File") { const indexPath = path.resolve(staticRoot, "index.html"); const indexData = yield* fileSystem .readFile(indexPath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!indexData) { return HttpServerResponse.text("Not Found", { status: 404 }); } return HttpServerResponse.uint8Array(indexData, { contentType: "text/html; charset=utf-8", }); } const contentType = Mime.getType(filePath) ?? "application/octet-stream"; const data = yield* fileSystem .readFile(filePath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!data) { return HttpServerResponse.text("Internal Server Error", { status: 500 }); } return HttpServerResponse.uint8Array(data, { contentType }); }); const httpServer = http.createServer(); const listenOptions = host ? { host, port } : { port }; const orchestrationEngine = yield* OrchestrationEngineService; const projectionReadModelQuery = yield* ProjectionSnapshotQuery; const checkpointDiffQuery = yield* CheckpointDiffQuery;Lines 1–500 of 905. Load more
/** * Server - HTTP/WebSocket server service interface. * * Owns startup and shutdown lifecycle of the HTTP server, static asset serving, * and WebSocket request routing. * * @module Server */ import http from "node:http"; import Mime from "@effect/platform-node/Mime"; import { CommandId, DEFAULT_PROVIDER_INTERACTION_MODE, type ClientOrchestrationCommand, type OrchestrationCommand, ORCHESTRATION_WS_CHANNELS, ORCHESTRATION_WS_METHODS, PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, ProjectId, ThreadId, WS_CHANNELS, WS_METHODS, WebSocketRequest, type WsResponse as WsResponseMessage, WsResponse, type WsPushEnvelopeBase, } from "@t3tools/contracts"; import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; import * as Socket from "effect/unstable/socket/Socket"; import { Cause, Effect, Exit, FileSystem, Layer, Option, Path, Ref, Result, Schema, Scope, ServiceMap, Stream, Struct, } from "effect"; import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { createLogger } from "./logger"; import { GitManager } from "./git/Services/GitManager.ts"; import { TerminalManager } from "./terminal/Services/Manager.ts"; import { Keybindings } from "./keybindings"; import { searchWorkspaceEntries } from "./workspaceEntries"; import { OrchestrationEngineService } from "./orchestration/Services/OrchestrationEngine"; import { ProjectionSnapshotQuery } from "./orchestration/Services/ProjectionSnapshotQuery"; import { OrchestrationReactor } from "./orchestration/Services/OrchestrationReactor"; import { ProviderService } from "./provider/Services/ProviderService"; import { ProviderHealth } from "./provider/Services/ProviderHealth"; import { CheckpointDiffQuery } from "./checkpointing/Services/CheckpointDiffQuery"; import { clamp } from "effect/Number"; import { Open, resolveAvailableEditors } from "./open"; import { ServerConfig } from "./config"; import { GitCore } from "./git/Services/GitCore.ts"; import { handleProjectFaviconRequest } from "./projectFaviconRoute"; import { ATTACHMENTS_ROUTE_PREFIX, normalizeAttachmentRelativePath, resolveAttachmentRelativePath, } from "./attachmentPaths"; import { createAttachmentId, resolveAttachmentPath, resolveAttachmentPathById, } from "./attachmentStore.ts"; import { parseBase64DataUrl } from "./imageMime.ts"; import { AnalyticsService } from "./telemetry/Services/AnalyticsService.ts"; import { expandHomePath } from "./os-jank.ts"; import { makeServerPushBus, type ServerPushClient } from "./wsServer/pushBus.ts"; import { makeServerReadiness } from "./wsServer/readiness.ts"; import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; /** * ServerShape - Service API for server lifecycle control. */ export interface ServerShape { /** * Start HTTP and WebSocket listeners. */ readonly start: Effect.Effect< http.Server, ServerLifecycleError, Scope.Scope | ServerRuntimeServices | ServerConfig | FileSystem.FileSystem | Path.Path >; /** * Wait for process shutdown signals. */ readonly stopSignal: Effect.Effect<void, never>; } /** * Server - Service tag for HTTP/WebSocket lifecycle management. */ export class Server extends ServiceMap.Service<Server, ServerShape>()("t3/wsServer/Server") {} function websocketRawToString(raw: string | Uint8Array): string { if (typeof raw === "string") { return raw; } return Buffer.from(raw).toString("utf8"); } function toPosixRelativePath(input: string): string { return input.replaceAll("\\", "/"); } function resolveWorkspaceWritePath(params: { workspaceRoot: string; relativePath: string; path: Path.Path; }): Effect.Effect<{ absolutePath: string; relativePath: string }, RouteRequestError> { const normalizedInputPath = params.relativePath.trim(); if (params.path.isAbsolute(normalizedInputPath)) { return Effect.fail( new RouteRequestError({ message: "Workspace file path must be relative to the project root.", }), ); } const absolutePath = params.path.resolve(params.workspaceRoot, normalizedInputPath); const relativeToRoot = toPosixRelativePath( params.path.relative(params.workspaceRoot, absolutePath), ); if ( relativeToRoot.length === 0 || relativeToRoot === "." || relativeToRoot.startsWith("../") || relativeToRoot === ".." || params.path.isAbsolute(relativeToRoot) ) { return Effect.fail( new RouteRequestError({ message: "Workspace file path must stay within the project root.", }), ); } return Effect.succeed({ absolutePath, relativePath: relativeToRoot, }); } function stripRequestTag<T extends { _tag: string }>(body: T) { return Struct.omit(body, ["_tag"]); } const encodeWsResponse = Schema.encodeEffect(Schema.fromJsonString(WsResponse)); const decodeWebSocketRequest = decodeJsonResult(WebSocketRequest); export type ServerCoreRuntimeServices = | OrchestrationEngineService | ProjectionSnapshotQuery | CheckpointDiffQuery | OrchestrationReactor | ProviderService | ProviderHealth; export type ServerRuntimeServices = | ServerCoreRuntimeServices | GitManager | GitCore | TerminalManager | Keybindings | Open | AnalyticsService; export class ServerLifecycleError extends Schema.TaggedErrorClass<ServerLifecycleError>()( "ServerLifecycleError", { operation: Schema.String, cause: Schema.optional(Schema.Defect), }, ) {} class RouteRequestError extends Schema.TaggedErrorClass<RouteRequestError>()("RouteRequestError", { message: Schema.String, }) {} export const createServer = Effect.fn(function* (): Effect.fn.Return< http.Server, ServerLifecycleError, Scope.Scope | ServerRuntimeServices | ServerConfig | FileSystem.FileSystem | Path.Path > { const serverConfig = yield* ServerConfig; const { port, cwd, keybindingsConfigPath, staticDir, devUrl, authToken, host, logWebSocketEvents, autoBootstrapProjectFromCwd, } = serverConfig; const availableEditors = resolveAvailableEditors(); const gitManager = yield* GitManager; const terminalManager = yield* TerminalManager; const keybindingsManager = yield* Keybindings; const providerHealth = yield* ProviderHealth; const git = yield* GitCore; const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* keybindingsManager.syncDefaultKeybindingsOnStartup.pipe( Effect.catch((error) => Effect.logWarning("failed to sync keybindings defaults on startup", { path: error.configPath, detail: error.detail, cause: error.cause, }), ), ); const providerStatuses = yield* providerHealth.getStatuses; const clients = yield* Ref.make(new Set<ServerPushClient>()); const logger = createLogger("ws"); const readiness = yield* makeServerReadiness; function logOutgoingPush(push: WsPushEnvelopeBase, recipients: number) { if (!logWebSocketEvents) return; logger.event("outgoing push", { channel: push.channel, sequence: push.sequence, recipients, payload: push.data, }); } const pushBus = yield* makeServerPushBus({ clients, logOutgoingPush, }); yield* readiness.markPushBusReady; yield* keybindingsManager.start.pipe( Effect.mapError( (cause) => new ServerLifecycleError({ operation: "keybindingsRuntimeStart", cause }), ), ); yield* readiness.markKeybindingsReady; const normalizeDispatchCommand = Effect.fnUntraced(function* (input: { readonly command: ClientOrchestrationCommand; }) { const normalizeProjectWorkspaceRoot = Effect.fnUntraced(function* (workspaceRoot: string) { const normalizedWorkspaceRoot = path.resolve(yield* expandHomePath(workspaceRoot.trim())); const workspaceStat = yield* fileSystem .stat(normalizedWorkspaceRoot) .pipe(Effect.catch(() => Effect.succeed(null))); if (!workspaceStat) { return yield* new RouteRequestError({ message: `Project directory does not exist: ${normalizedWorkspaceRoot}`, }); } if (workspaceStat.type !== "Directory") { return yield* new RouteRequestError({ message: `Project path is not a directory: ${normalizedWorkspaceRoot}`, }); } return normalizedWorkspaceRoot; }); if (input.command.type === "project.create") { return { ...input.command, workspaceRoot: yield* normalizeProjectWorkspaceRoot(input.command.workspaceRoot), } satisfies OrchestrationCommand; } if (input.command.type === "project.meta.update" && input.command.workspaceRoot !== undefined) { return { ...input.command, workspaceRoot: yield* normalizeProjectWorkspaceRoot(input.command.workspaceRoot), } satisfies OrchestrationCommand; } if (input.command.type !== "thread.turn.start") { return input.command as OrchestrationCommand; } const turnStartCommand = input.command; const normalizedAttachments = yield* Effect.forEach( turnStartCommand.message.attachments, (attachment) => Effect.gen(function* () { const parsed = parseBase64DataUrl(attachment.dataUrl); if (!parsed || !parsed.mimeType.startsWith("image/")) { return yield* new RouteRequestError({ message: `Invalid image attachment payload for '${attachment.name}'.`, }); } const bytes = Buffer.from(parsed.base64, "base64"); if (bytes.byteLength === 0 || bytes.byteLength > PROVIDER_SEND_TURN_MAX_IMAGE_BYTES) { return yield* new RouteRequestError({ message: `Image attachment '${attachment.name}' is empty or too large.`, }); } const attachmentId = createAttachmentId(turnStartCommand.threadId); if (!attachmentId) { return yield* new RouteRequestError({ message: "Failed to create a safe attachment id.", }); } const persistedAttachment = { type: "image" as const, id: attachmentId, name: attachment.name, mimeType: parsed.mimeType.toLowerCase(), sizeBytes: bytes.byteLength, }; const attachmentPath = resolveAttachmentPath({ stateDir: serverConfig.stateDir, attachment: persistedAttachment, }); if (!attachmentPath) { return yield* new RouteRequestError({ message: `Failed to resolve persisted path for '${attachment.name}'.`, }); } yield* fileSystem.makeDirectory(path.dirname(attachmentPath), { recursive: true }).pipe( Effect.mapError( () => new RouteRequestError({ message: `Failed to create attachment directory for '${attachment.name}'.`, }), ), ); yield* fileSystem.writeFile(attachmentPath, bytes).pipe( Effect.mapError( () => new RouteRequestError({ message: `Failed to persist attachment '${attachment.name}'.`, }), ), ); return persistedAttachment; }), { concurrency: 1 }, ); return { ...turnStartCommand, message: { ...turnStartCommand.message, attachments: normalizedAttachments, }, } satisfies OrchestrationCommand; }); const serveHttpRequest = Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const url = HttpServerRequest.toURL(request); if (!url) { return HttpServerResponse.text("Bad Request", { status: 400 }); } if (url.pathname === "/api/project-favicon") { return yield* handleProjectFaviconRequest(url.searchParams.get("cwd")); } if (url.pathname.startsWith(ATTACHMENTS_ROUTE_PREFIX)) { const rawRelativePath = url.pathname.slice(ATTACHMENTS_ROUTE_PREFIX.length); const normalizedRelativePath = normalizeAttachmentRelativePath(rawRelativePath); if (!normalizedRelativePath) { return HttpServerResponse.text("Invalid attachment path", { status: 400 }); } const isIdLookup = !normalizedRelativePath.includes("/") && !normalizedRelativePath.includes("."); const filePath = isIdLookup ? resolveAttachmentPathById({ stateDir: serverConfig.stateDir, attachmentId: normalizedRelativePath, }) : resolveAttachmentRelativePath({ stateDir: serverConfig.stateDir, relativePath: normalizedRelativePath, }); if (!filePath) { return HttpServerResponse.text(isIdLookup ? "Not Found" : "Invalid attachment path", { status: isIdLookup ? 404 : 400, }); } const fileInfo = yield* fileSystem .stat(filePath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!fileInfo || fileInfo.type !== "File") { return HttpServerResponse.text("Not Found", { status: 404 }); } const contentType = Mime.getType(filePath) ?? "application/octet-stream"; const data = yield* fileSystem .readFile(filePath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!data) { return HttpServerResponse.text("Internal Server Error", { status: 500 }); } return HttpServerResponse.uint8Array(data, { contentType, headers: { "Cache-Control": "public, max-age=31536000, immutable" }, }); } if (devUrl) { return HttpServerResponse.redirect(devUrl.href, { status: 302 }); } if (!staticDir) { return HttpServerResponse.text("No static directory configured and no dev URL set.", { status: 503, }); } const staticRoot = path.resolve(staticDir); const staticRequestPath = url.pathname === "/" ? "/index.html" : url.pathname; const rawStaticRelativePath = staticRequestPath.replace(/^[/\\]+/, ""); const hasRawLeadingParentSegment = rawStaticRelativePath.startsWith(".."); const staticRelativePath = path.normalize(rawStaticRelativePath).replace(/^[/\\]+/, ""); const hasPathTraversalSegment = staticRelativePath.startsWith(".."); if ( staticRelativePath.length === 0 || hasRawLeadingParentSegment || hasPathTraversalSegment || staticRelativePath.includes("\0") ) { return HttpServerResponse.text("Invalid static file path", { status: 400 }); } const isWithinStaticRoot = (candidate: string) => candidate === staticRoot || candidate.startsWith(staticRoot.endsWith(path.sep) ? staticRoot : `${staticRoot}${path.sep}`); let filePath = path.resolve(staticRoot, staticRelativePath); if (!isWithinStaticRoot(filePath)) { return HttpServerResponse.text("Invalid static file path", { status: 400 }); } const ext = path.extname(filePath); if (!ext) { filePath = path.resolve(filePath, "index.html"); if (!isWithinStaticRoot(filePath)) { return HttpServerResponse.text("Invalid static file path", { status: 400 }); } } const fileInfo = yield* fileSystem .stat(filePath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!fileInfo || fileInfo.type !== "File") { const indexPath = path.resolve(staticRoot, "index.html"); const indexData = yield* fileSystem .readFile(indexPath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!indexData) { return HttpServerResponse.text("Not Found", { status: 404 }); } return HttpServerResponse.uint8Array(indexData, { contentType: "text/html; charset=utf-8", }); } const contentType = Mime.getType(filePath) ?? "application/octet-stream"; const data = yield* fileSystem .readFile(filePath) .pipe(Effect.catch(() => Effect.succeed(null))); if (!data) { return HttpServerResponse.text("Internal Server Error", { status: 500 }); } return HttpServerResponse.uint8Array(data, { contentType }); }); const httpServer = http.createServer(); const listenOptions = host ? { host, port } : { port }; const orchestrationEngine = yield* OrchestrationEngineService; const projectionReadModelQuery = yield* ProjectionSnapshotQuery; const checkpointDiffQuery = yield* CheckpointDiffQuery;