4022 lines · 133.8 KB · TypeScript
RawDownload
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";import * as NodeSocket from "@effect/platform-node/NodeSocket";import * as NodeServices from "@effect/platform-node/NodeServices"; import {  CommandId,  DEFAULT_SERVER_SETTINGS,  EnvironmentId,  EventId,  GitCommandError,  KeybindingRule,  MessageId,  OpenError,  type OrchestrationThreadShell,  TerminalNotRunningError,  type OrchestrationCommand,  type OrchestrationEvent,  ORCHESTRATION_WS_METHODS,  ProjectId,  ResolvedKeybindingRule,  ThreadId,  WS_METHODS,  WsRpcGroup,  EditorId,} from "@t3tools/contracts";import { assert, it } from "@effect/vitest";import { assertFailure, assertInclude, assertTrue } from "@effect/vitest/utils";import {  Deferred,  Duration,  Effect,  FileSystem,  Layer,  ManagedRuntime,  Option,  Path,  Stream,} from "effect";import {  FetchHttpClient,  HttpBody,  HttpClient,  HttpRouter,  HttpServer,} from "effect/unstable/http";import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability";import { RpcClient, RpcSerialization } from "effect/unstable/rpc";import * as Socket from "effect/unstable/socket/Socket";import { vi } from "vitest"; import type { ServerConfigShape } from "./config.ts";import { deriveServerPaths, ServerConfig } from "./config.ts";import { makeRoutesLayer } from "./server.ts";import { resolveAttachmentRelativePath } from "./attachmentPaths.ts";import {  CheckpointDiffQuery,  type CheckpointDiffQueryShape,} from "./checkpointing/Services/CheckpointDiffQuery.ts";import { GitCore, type GitCoreShape } from "./git/Services/GitCore.ts";import { GitManager, type GitManagerShape } from "./git/Services/GitManager.ts";import { GitStatusBroadcasterLive } from "./git/Layers/GitStatusBroadcaster.ts";import {  GitStatusBroadcaster,  type GitStatusBroadcasterShape,} from "./git/Services/GitStatusBroadcaster.ts";import { Keybindings, type KeybindingsShape } from "./keybindings.ts";import { Open, type OpenShape } from "./open.ts";import {  OrchestrationEngineService,  type OrchestrationEngineShape,} from "./orchestration/Services/OrchestrationEngine.ts";import { OrchestrationListenerCallbackError } from "./orchestration/Errors.ts";import {  ProjectionSnapshotQuery,  type ProjectionSnapshotQueryShape,} from "./orchestration/Services/ProjectionSnapshotQuery.ts";import { SqlitePersistenceMemory } from "./persistence/Layers/Sqlite.ts";import {  ProviderRegistry,  type ProviderRegistryShape,} from "./provider/Services/ProviderRegistry.ts";import { ServerLifecycleEvents, type ServerLifecycleEventsShape } from "./serverLifecycleEvents.ts";import { ServerRuntimeStartup, type ServerRuntimeStartupShape } from "./serverRuntimeStartup.ts";import { ServerSettingsService, type ServerSettingsShape } from "./serverSettings.ts";import { TerminalManager, type TerminalManagerShape } from "./terminal/Services/Manager.ts";import {  BrowserTraceCollector,  type BrowserTraceCollectorShape,} from "./observability/Services/BrowserTraceCollector.ts";import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts";import {  ProjectSetupScriptRunner,  type ProjectSetupScriptRunnerShape,} from "./project/Services/ProjectSetupScriptRunner.ts";import {  RepositoryIdentityResolver,  type RepositoryIdentityResolverShape,} from "./project/Services/RepositoryIdentityResolver.ts";import {  ServerEnvironment,  type ServerEnvironmentShape,} from "./environment/Services/ServerEnvironment.ts";import { WorkspaceEntriesLive } from "./workspace/Layers/WorkspaceEntries.ts";import { WorkspaceFileSystemLive } from "./workspace/Layers/WorkspaceFileSystem.ts";import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";import { ServerSecretStoreLive } from "./auth/Layers/ServerSecretStore.ts";import { ServerAuthLive } from "./auth/Layers/ServerAuth.ts"; const defaultProjectId = ProjectId.make("project-default");const defaultThreadId = ThreadId.make("thread-default");const defaultDesktopBootstrapToken = "test-desktop-bootstrap-token";const defaultModelSelection = {  provider: "codex",  model: "gpt-5-codex",} as const;const testEnvironmentDescriptor = {  environmentId: EnvironmentId.make("environment-test"),  label: "Test environment",  platform: {    os: "darwin" as const,    arch: "arm64" as const,  },  serverVersion: "0.0.0-test",  capabilities: {    repositoryIdentity: true,  },};const makeDefaultOrchestrationReadModel = () => {  const now = new Date().toISOString();  return {    snapshotSequence: 0,    updatedAt: now,    projects: [      {        id: defaultProjectId,        title: "Default Project",        workspaceRoot: "/tmp/default-project",        defaultModelSelection,        scripts: [],        createdAt: now,        updatedAt: now,        deletedAt: null,      },    ],    threads: [      {        id: defaultThreadId,        projectId: defaultProjectId,        title: "Default Thread",        modelSelection: defaultModelSelection,        interactionMode: "default" as const,        runtimeMode: "full-access" as const,        branch: null,        worktreePath: null,        createdAt: now,        updatedAt: now,        archivedAt: null,        latestTurn: null,        messages: [],        session: null,        activities: [],        proposedPlans: [],        checkpoints: [],        deletedAt: null,      },    ],  };}; const makeDefaultOrchestrationThreadShell = (  overrides: Partial<OrchestrationThreadShell> = {},): OrchestrationThreadShell => {  const now = new Date().toISOString();  return {    id: defaultThreadId,    projectId: defaultProjectId,    title: "Default Thread",    modelSelection: defaultModelSelection,    runtimeMode: "full-access",    interactionMode: "default",    branch: null,    worktreePath: null,    latestTurn: null,    createdAt: now,    updatedAt: now,    archivedAt: null,    session: null,    latestUserMessageAt: null,    hasPendingApprovals: false,    hasPendingUserInput: false,    hasActionableProposedPlan: false,    ...overrides,  };}; const workspaceAndProjectServicesLayer = Layer.mergeAll(  WorkspacePathsLive,  WorkspaceEntriesLive.pipe(Layer.provide(WorkspacePathsLive)),  WorkspaceFileSystemLive.pipe(    Layer.provide(WorkspacePathsLive),    Layer.provide(WorkspaceEntriesLive.pipe(Layer.provide(WorkspacePathsLive))),  ),  ProjectFaviconResolverLive,); const browserOtlpTracingLayer = Layer.mergeAll(  FetchHttpClient.layer,  OtlpSerialization.layerJson,  Layer.succeed(HttpClient.TracerDisabledWhen, () => true),); const authTestLayer = ServerAuthLive.pipe(  Layer.provide(SqlitePersistenceMemory),  Layer.provide(ServerSecretStoreLive),); const makeBrowserOtlpPayload = (spanName: string) =>  Effect.gen(function* () {    const collector = yield* Effect.acquireRelease(      Effect.promise(async () => {        const NodeHttp = await import("node:http");         return await new Promise<{          readonly close: () => Promise<void>;          readonly firstRequest: Promise<{            readonly body: string;            readonly contentType: string | null;          }>;          readonly url: string;        }>((resolve, reject) => {          let resolveFirstRequest:            | ((request: { readonly body: string; readonly contentType: string | null }) => void)            | undefined;          const firstRequest = new Promise<{            readonly body: string;            readonly contentType: string | null;          }>((resolveRequest) => {            resolveFirstRequest = resolveRequest;          });           const server = NodeHttp.createServer((request, response) => {            const chunks: Buffer[] = [];            request.on("data", (chunk) => {              chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));            });            request.on("end", () => {              resolveFirstRequest?.({                body: Buffer.concat(chunks).toString("utf8"),                contentType: request.headers["content-type"] ?? null,              });              resolveFirstRequest = undefined;              response.statusCode = 204;              response.end();            });          });           server.on("error", reject);          server.listen(0, "127.0.0.1", () => {            const address = server.address();            if (!address || typeof address === "string") {              reject(new Error("Expected TCP collector address"));              return;            }             resolve({              url: `http://127.0.0.1:${address.port}/v1/traces`,              firstRequest,              close: () =>                new Promise<void>((resolveClose, rejectClose) => {                  server.close((error) => {                    if (error) {                      rejectClose(error);                      return;                    }                    resolveClose();                  });                }),            });          });        });      }),      ({ close }) => Effect.promise(close),    );     const runtime = ManagedRuntime.make(      OtlpTracer.layer({        url: collector.url,        exportInterval: "10 millis",        resource: {          serviceName: "t3-web",          attributes: {            "service.runtime": "t3-web",            "service.mode": "browser",            "service.version": "test",          },        },      }).pipe(Layer.provide(browserOtlpTracingLayer)),    );     try {      yield* Effect.promise(() => runtime.runPromise(Effect.void.pipe(Effect.withSpan(spanName))));    } finally {      yield* Effect.promise(() => runtime.dispose());    }     const request = yield* Effect.promise(() =>      Promise.race([        collector.firstRequest,        new Promise<never>((_, reject) => {          setTimeout(() => reject(new Error("Timed out waiting for OTLP trace export")), 1_000);        }),      ]),    );     return JSON.parse(request.body) as OtlpTracer.TraceData;  }); const buildAppUnderTest = (options?: {  config?: Partial<ServerConfigShape>;  layers?: {    keybindings?: Partial<KeybindingsShape>;    providerRegistry?: Partial<ProviderRegistryShape>;    serverSettings?: Partial<ServerSettingsShape>;    open?: Partial<OpenShape>;    gitCore?: Partial<GitCoreShape>;    gitManager?: Partial<GitManagerShape>;    gitStatusBroadcaster?: Partial<GitStatusBroadcasterShape>;    projectSetupScriptRunner?: Partial<ProjectSetupScriptRunnerShape>;    terminalManager?: Partial<TerminalManagerShape>;    orchestrationEngine?: Partial<OrchestrationEngineShape>;    projectionSnapshotQuery?: Partial<ProjectionSnapshotQueryShape>;    checkpointDiffQuery?: Partial<CheckpointDiffQueryShape>;    browserTraceCollector?: Partial<BrowserTraceCollectorShape>;    serverLifecycleEvents?: Partial<ServerLifecycleEventsShape>;    serverRuntimeStartup?: Partial<ServerRuntimeStartupShape>;    serverEnvironment?: Partial<ServerEnvironmentShape>;    repositoryIdentityResolver?: Partial<RepositoryIdentityResolverShape>;  };}) =>  Effect.gen(function* () {    const fileSystem = yield* FileSystem.FileSystem;    const tempBaseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-router-test-" });    const baseDir = options?.config?.baseDir ?? tempBaseDir;    const devUrl = options?.config?.devUrl;    const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);    const config: ServerConfigShape = {      logLevel: "Info",      traceMinLevel: "Info",      traceTimingEnabled: true,      traceBatchWindowMs: 200,      traceMaxBytes: 10 * 1024 * 1024,      traceMaxFiles: 10,      otlpTracesUrl: undefined,      otlpMetricsUrl: undefined,      otlpExportIntervalMs: 10_000,      otlpServiceName: "t3-server",      mode: "desktop",      port: 0,      host: "127.0.0.1",      cwd: process.cwd(),      baseDir,      ...derivedPaths,      staticDir: undefined,      devUrl,      noBrowser: true,      startupPresentation: "browser",      desktopBootstrapToken: defaultDesktopBootstrapToken,      autoBootstrapProjectFromCwd: false,      logWebSocketEvents: false,      ...options?.config,    };    const layerConfig = Layer.succeed(ServerConfig, config);    const gitCoreLayer = Layer.mock(GitCore)({      isInsideWorkTree: () => Effect.succeed(false),      listWorkspaceFiles: () =>        Effect.succeed({          paths: [],          truncated: false,        }),      filterIgnoredPaths: (_cwd, relativePaths) => Effect.succeed(relativePaths),      ...options?.layers?.gitCore,    });    const gitManagerLayer = Layer.mock(GitManager)({      ...options?.layers?.gitManager,    });    const workspaceEntriesLayer = WorkspaceEntriesLive.pipe(      Layer.provide(WorkspacePathsLive),      Layer.provideMerge(gitCoreLayer),    );    const workspaceAndProjectServicesLayer = Layer.mergeAll(      WorkspacePathsLive,      workspaceEntriesLayer,      WorkspaceFileSystemLive.pipe(        Layer.provide(WorkspacePathsLive),        Layer.provide(workspaceEntriesLayer),      ),      ProjectFaviconResolverLive,    );    const gitStatusBroadcasterLayer = options?.layers?.gitStatusBroadcaster      ? Layer.mock(GitStatusBroadcaster)({          ...options.layers.gitStatusBroadcaster,        })      : GitStatusBroadcasterLive.pipe(Layer.provide(gitManagerLayer));     const servedRoutesLayer = HttpRouter.serve(makeRoutesLayer, {      disableListenLog: true,      disableLogger: true,    }).pipe(      Layer.provide(        Layer.mock(Keybindings)({          loadConfigState: Effect.succeed({            keybindings: [],            issues: [],          }),          streamChanges: Stream.empty,          ...options?.layers?.keybindings,        }),      ),      Layer.provide(        Layer.mock(ProviderRegistry)({          getProviders: Effect.succeed([]),          refresh: () => Effect.succeed([]),          streamChanges: Stream.empty,          ...options?.layers?.providerRegistry,        }),      ),      Layer.provide(        Layer.mock(ServerSettingsService)({          start: Effect.void,          ready: Effect.void,          getSettings: Effect.succeed(DEFAULT_SERVER_SETTINGS),          updateSettings: () => Effect.succeed(DEFAULT_SERVER_SETTINGS),          streamChanges: Stream.empty,          ...options?.layers?.serverSettings,        }),      ),      Layer.provide(        Layer.mock(Open)({          ...options?.layers?.open,        }),      ),      Layer.provide(gitCoreLayer),      Layer.provide(gitManagerLayer),      Layer.provideMerge(gitStatusBroadcasterLayer),      Layer.provide(        Layer.mock(ProjectSetupScriptRunner)({          runForThread: () => Effect.succeed({ status: "no-script" as const }),          ...options?.layers?.projectSetupScriptRunner,        }),      ),      Layer.provide(        Layer.mock(TerminalManager)({          ...options?.layers?.terminalManager,        }),      ),      Layer.provide(        Layer.mock(OrchestrationEngineService)({          getReadModel: () => Effect.succeed(makeDefaultOrchestrationReadModel()),          readEvents: () => Stream.empty,          dispatch: () => Effect.succeed({ sequence: 0 }),          streamDomainEvents: Stream.empty,          ...options?.layers?.orchestrationEngine,        }),      ),      Layer.provide(        Layer.mock(ProjectionSnapshotQuery)({          getSnapshot: () => Effect.succeed(makeDefaultOrchestrationReadModel()),          getShellSnapshot: () =>            Effect.succeed({              snapshotSequence: 0,              projects: [],              threads: [],              updatedAt: new Date(0).toISOString(),            }),          getProjectShellById: () => Effect.succeed(Option.none()),          getThreadShellById: () => Effect.succeed(Option.none()),          getThreadDetailById: () => Effect.succeed(Option.none()),          getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }),          getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()),          getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()),          getThreadCheckpointContext: () => Effect.succeed(Option.none()),          ...options?.layers?.projectionSnapshotQuery,        }),      ),      Layer.provide(        Layer.mock(CheckpointDiffQuery)({          getTurnDiff: () =>            Effect.succeed({              threadId: defaultThreadId,              fromTurnCount: 0,              toTurnCount: 0,              diff: "",            }),          getFullThreadDiff: () =>            Effect.succeed({              threadId: defaultThreadId,              fromTurnCount: 0,              toTurnCount: 0,              diff: "",            }),

Lines 1–500 of 4022.