3216 lines · 124.3 KB · TypeScript
RawDownload
import * as NodeServices from "@effect/platform-node/NodeServices";import * as NodeOS from "node:os";import { describe, expect, it } from "@effect/vitest";import {  type OrchestrationProjectShell,  ProjectId,  ProviderDriverKind,  ProviderInstanceId,  type ServerSettings as ContractServerSettings,} from "@t3tools/contracts";import { symlinksSupported } from "@t3tools/shared/testing/symlinks";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream";import * as TestClock from "effect/testing/TestClock"; import * as ServerConfig from "../config.ts";import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";import * as ServerSettings from "../serverSettings.ts";import * as AgentSessionScanner from "./AgentSessionScanner.ts"; const makeProjectShell = (workspaceRoot: string): OrchestrationProjectShell => ({  id: ProjectId.make("project-1"),  title: "Imported",  workspaceRoot,  defaultModelSelection: null,  scripts: [],  createdAt: "2026-01-01T00:00:00.000Z",  updatedAt: "2026-01-01T00:00:00.000Z",}); /** Only `getShellSnapshot` is exercised; the rest must not be called. */const makeProjectionSnapshotQueryLayer = (importedWorkspaceRoots: ReadonlyArray<string>) =>  Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, {    getCommandReadModel: () => Effect.die("unused"),    getUserInputActivity: () => Effect.die("unused"),    getSnapshot: () => Effect.die("unused"),    getShellSnapshot: () =>      Effect.succeed({        snapshotSequence: 0,        projects: importedWorkspaceRoots.map((workspaceRoot) => makeProjectShell(workspaceRoot)),        threads: [],        updatedAt: "2026-01-01T00:00:00.000Z",      }),    getArchivedShellSnapshot: () => Effect.die("unused"),    getSnapshotSequence: () => Effect.die("unused"),    getCounts: () => Effect.die("unused"),    getEventReplayStats: () => Effect.die("unused"),    getActiveProjectByWorkspaceRoot: () => Effect.die("unused"),    getProjectShellById: () => Effect.die("unused"),    getImportedAgentSessionSources: () => Effect.succeed([]),    getFirstActiveThreadIdByProjectId: () => Effect.die("unused"),    getThreadCheckpointContext: () => Effect.die("unused"),    getFullThreadDiffContext: () => Effect.die("unused"),    getThreadShellById: () => Effect.die("unused"),    getThreadRuntimeContext: () => Effect.die("unused"),    getTurnStartMessage: () => Effect.die("unused"),    getThreadDetailById: () => Effect.die("unused"),    getThreadDetailSnapshot: () => Effect.die("unused"),    searchThreads: () => Effect.die("unused"),  }); /** * Run a scan against the given homes. Homes are temp dirs created inside the * test, so the layer is built per run rather than shared. */interface ScannerTestInput {  readonly claudeHomePath: string;  readonly codexHomePath: string;  readonly importedWorkspaceRoots?: ReadonlyArray<string>;  /** Base dir for the test ServerConfig; worktreesDir derives from it. */  readonly configBaseDir?: string;  readonly providerInstances?: ContractServerSettings["providerInstances"];} const makeScannerTestLayer = (input: ScannerTestInput) =>  AgentSessionScanner.layer.pipe(    Layer.provide(      Layer.mergeAll(        ServerSettings.layerTest({          providers: {            claudeAgent: { homePath: input.claudeHomePath },            codex: { homePath: input.codexHomePath },          },          ...(input.providerInstances === undefined            ? {}            : { providerInstances: input.providerInstances }),        }),        ServerConfig.layerTest(          input.claudeHomePath,          input.configBaseDir ?? { prefix: "t3code-scanner-config-" },        ),        makeProjectionSnapshotQueryLayer(input.importedWorkspaceRoots ?? []),      ),    ),  ); const runScan = (input: ScannerTestInput) =>  Effect.gen(function* () {    const scanner = yield* AgentSessionScanner.AgentSessionScanner;    return yield* scanner.scan;  }).pipe(Effect.provide(makeScannerTestLayer(input))); const runRecentThreadOutcomes = (input: ScannerTestInput & { readonly workspaceRoot: string }) =>  Effect.gen(function* () {    const scanner = yield* AgentSessionScanner.AgentSessionScanner;    return yield* scanner.recentThreads(input.workspaceRoot).pipe(      Stream.runCollect,      Effect.map((outcomes) => Array.from(outcomes)),    );  }).pipe(Effect.provide(makeScannerTestLayer(input))); const runRecentThreads = (input: ScannerTestInput & { readonly workspaceRoot: string }) =>  runRecentThreadOutcomes(input).pipe(    Effect.map((outcomes) =>      outcomes.flatMap((outcome) => (outcome._tag === "Importable" ? [outcome.thread] : [])),    ),  ); const makeTempDir = Effect.fn("AgentSessionScanner.test.makeTempDir")(function* (prefix: string) {  const fileSystem = yield* FileSystem.FileSystem;  return yield* fileSystem.makeTempDirectoryScoped({ prefix });}); const writeTranscript = Effect.fn("AgentSessionScanner.test.writeTranscript")(function* (input: {  readonly filePath: string;  readonly contents: string;  /** Epoch millis, so ordering assertions never depend on write timing. */  readonly mtimeMs: number;}) {  const fileSystem = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  yield* fileSystem.makeDirectory(path.dirname(input.filePath), { recursive: true });  yield* fileSystem.writeFileString(input.filePath, input.contents);  // Numeric utimes arguments are seconds, not milliseconds.  const seconds = input.mtimeMs / 1000;  yield* fileSystem.utimes(input.filePath, seconds, seconds);}); /** Claude session line: the first record carries the real `cwd`. */const claudeSessionLine = (cwd: string) =>  `${JSON.stringify({ type: "user", cwd, sessionId: "s1" })}\n${JSON.stringify({ type: "assistant" })}\n`; /** Codex rollout line: session metadata is nested under `payload`. */const codexRolloutLine = (cwd: string) =>  `${JSON.stringify({ timestamp: "2026-01-01T00:00:00.000Z", type: "session_meta", payload: { id: "r1", cwd } })}\n`; const encodeTranscriptRecord = Schema.encodeUnknownSync(Schema.fromJsonString(Schema.Unknown)); function makeRecordLimitTranscript(cwd: string, overflow: boolean): string {  const records =    [      encodeTranscriptRecord({        type: "session_meta",        payload: { id: "record-limit-session", cwd },      }),      encodeTranscriptRecord({        type: "event_msg",        payload: { type: "user_message", message: "First prompt" },      }),    ].join("\n") +    "\n" +    "{}\n".repeat(99_998);  return overflow    ? records +        "\n" +        encodeTranscriptRecord({          type: "event_msg",          payload: { type: "user_message", message: "Overflow prompt" },        }) +        "\n"    : records;} it.layer(NodeServices.layer)("AgentSessionScanner", (it) => {  describe("scan", () => {    it.effect("reads Claude project cwds from transcripts, newest first", () =>      Effect.gen(function* () {        const path = yield* Path.Path;        const claudeHomePath = yield* makeTempDir("t3code-claude-home-");        const codexHomePath = yield* makeTempDir("t3code-codex-home-");        const olderWorkspace = yield* makeTempDir("t3code-workspace-older-");        const newerWorkspace = yield* makeTempDir("t3code-workspace-newer-");         // Slugs are intentionally lossy; the scanner must not decode them.        yield* writeTranscript({          filePath: path.join(claudeHomePath, "projects", "-slug-older", "a.jsonl"),          contents: claudeSessionLine(olderWorkspace),          mtimeMs: Date.parse("2026-01-01T00:00:00.000Z"),        });        yield* writeTranscript({          filePath: path.join(claudeHomePath, "projects", "-slug-older", "b.jsonl"),          contents: claudeSessionLine(olderWorkspace),          mtimeMs: Date.parse("2026-01-02T00:00:00.000Z"),        });        yield* writeTranscript({          filePath: path.join(claudeHomePath, "projects", "-slug-newer", "c.jsonl"),          contents: claudeSessionLine(newerWorkspace),          mtimeMs: Date.parse("2026-03-01T00:00:00.000Z"),        });         const result = yield* runScan({ claudeHomePath, codexHomePath });         expect(result.candidates).toEqual([          {            path: newerWorkspace,            title: path.basename(newerWorkspace),            sources: ["claudeAgent"],            threadCount: 1,            lastActiveAt: "2026-03-01T00:00:00.000Z",            alreadyImported: false,            git: null,          },          {            path: olderWorkspace,            title: path.basename(olderWorkspace),            sources: ["claudeAgent"],            threadCount: 2,            lastActiveAt: "2026-01-02T00:00:00.000Z",            alreadyImported: false,            git: null,          },        ]);      }),    );     it.effect("groups Codex rollouts by cwd across date directories", () =>      Effect.gen(function* () {        const path = yield* Path.Path;        const claudeHomePath = yield* makeTempDir("t3code-claude-home-");        const codexHomePath = yield* makeTempDir("t3code-codex-home-");        const workspace = yield* makeTempDir("t3code-workspace-");        const otherWorkspace = yield* makeTempDir("t3code-workspace-other-");         const rollout = (year: string, month: string, day: string, name: string) =>          path.join(codexHomePath, "sessions", year, month, day, name);         yield* writeTranscript({          filePath: rollout("2026", "01", "05", "rollout-2026-01-05T10-00-00-aaa.jsonl"),          contents: codexRolloutLine(workspace),          mtimeMs: Date.parse("2026-01-05T10:00:00.000Z"),        });        yield* writeTranscript({          filePath: rollout("2026", "02", "09", "rollout-2026-02-09T10-00-00-bbb.jsonl"),          contents: codexRolloutLine(workspace),          mtimeMs: Date.parse("2026-02-09T10:00:00.000Z"),        });        yield* writeTranscript({          filePath: rollout("2026", "02", "09", "rollout-2026-02-09T11-00-00-ccc.jsonl"),          contents: codexRolloutLine(otherWorkspace),          mtimeMs: Date.parse("2026-02-09T11:00:00.000Z"),        });         const result = yield* runScan({ claudeHomePath, codexHomePath });         expect(result.candidates).toEqual([          {            path: otherWorkspace,            title: path.basename(otherWorkspace),            sources: ["codex"],            threadCount: 1,            lastActiveAt: "2026-02-09T11:00:00.000Z",            alreadyImported: false,            git: null,          },          {            path: workspace,            title: path.basename(workspace),            sources: ["codex"],            threadCount: 2,            lastActiveAt: "2026-02-09T10:00:00.000Z",            alreadyImported: false,            git: null,          },        ]);      }),    );     it.effect.each(["claudeAgent", "codex"] as const)(      "does not open a non-file %s transcript",      (source) =>        Effect.gen(function* () {          const path = yield* Path.Path;          const fileSystem = yield* FileSystem.FileSystem;          const claudeHomePath = yield* makeTempDir("t3code-claude-home-");          const codexHomePath = yield* makeTempDir("t3code-codex-home-");          const transcriptPath =            source === "claudeAgent"              ? path.join(claudeHomePath, "projects", "-slug", "session.jsonl")              : path.join(codexHomePath, "sessions", "2026", "08", "24", "rollout-session.jsonl");          yield* fileSystem.makeDirectory(transcriptPath, { recursive: true });           let transcriptOpenCount = 0;          const simulatedFileSystem = FileSystem.FileSystem.of({            ...fileSystem,            open: (filePath, options) => {              if (filePath === transcriptPath) transcriptOpenCount += 1;              return fileSystem.open(filePath, options);            },          });           const result = yield* runScan({ claudeHomePath, codexHomePath }).pipe(            Effect.provideService(FileSystem.FileSystem, simulatedFileSystem),          );           expect(result.candidates).toEqual([]);          expect(transcriptOpenCount).toBe(0);        }),    );     it.effect.each(["claudeAgent", "codex"] as const)(      "stops %s directory reads at the discovery operation budget",      (source) =>        Effect.gen(function* () {          const path = yield* Path.Path;          const fileSystem = yield* FileSystem.FileSystem;          const claudeHomePath = yield* makeTempDir("t3code-claude-home-");          const codexHomePath = yield* makeTempDir("t3code-codex-home-");          const discoveryRoot =            source === "claudeAgent"              ? path.join(claudeHomePath, "projects")              : path.join(codexHomePath, "sessions");          const emptyDirectories = Array.from(            { length: 20_001 },            (_, index) => `empty-${index.toString().padStart(5, "0")}`,          );          let directoryReadCount = 0;          const simulatedFileSystem = FileSystem.FileSystem.of({            ...fileSystem,            readDirectory: (directory, options) => {              if (directory === discoveryRoot) {                directoryReadCount += 1;                return Effect.succeed(emptyDirectories);              }              if (path.dirname(directory) === discoveryRoot) {                directoryReadCount += 1;                return Effect.succeed([]);              }              return fileSystem.readDirectory(directory, options);            },          });           const result = yield* runScan({ claudeHomePath, codexHomePath }).pipe(            Effect.provideService(FileSystem.FileSystem, simulatedFileSystem),          );           expect(result.candidates).toEqual([]);          expect(directoryReadCount).toBe(20_000);        }),    );     it.effect("merges the same cwd seen by both agents and flags imported projects", () =>      Effect.gen(function* () {        const path = yield* Path.Path;        const claudeHomePath = yield* makeTempDir("t3code-claude-home-");        const codexHomePath = yield* makeTempDir("t3code-codex-home-");        const workspace = yield* makeTempDir("t3code-workspace-");         yield* writeTranscript({          filePath: path.join(claudeHomePath, "projects", "-slug", "a.jsonl"),          contents: claudeSessionLine(workspace),          mtimeMs: Date.parse("2026-01-01T00:00:00.000Z"),        });        yield* writeTranscript({          filePath: path.join(            codexHomePath,            "sessions",            "2026",            "04",            "01",            "rollout-2026-04-01T09-00-00-aaa.jsonl",          ),          contents: codexRolloutLine(workspace),          mtimeMs: Date.parse("2026-04-01T09:00:00.000Z"),        });         const result = yield* runScan({          claudeHomePath,          codexHomePath,          importedWorkspaceRoots: [workspace],        });         expect(result.candidates).toEqual([          {            path: workspace,            title: path.basename(workspace),            projectId: ProjectId.make("project-1"),            sources: ["claudeAgent", "codex"],            threadCount: 2,            lastActiveAt: "2026-04-01T09:00:00.000Z",            alreadyImported: true,            git: null,          },        ]);      }),    );     it.effect("returns the imported project ID through a realpath alias", () =>      Effect.gen(function* () {        const path = yield* Path.Path;        const fileSystem = yield* FileSystem.FileSystem;        const claudeHomePath = yield* makeTempDir("t3code-claude-home-");        const codexHomePath = yield* makeTempDir("t3code-codex-home-");        const workspace = yield* makeTempDir("t3code-workspace-");        const linkParent = yield* makeTempDir("t3code-scanner-links-");        const workspaceAlias = path.join(linkParent, "workspace-alias");        yield* fileSystem.symlink(workspace, workspaceAlias);         yield* writeTranscript({          filePath: path.join(claudeHomePath, "projects", "-slug", "a.jsonl"),          contents: claudeSessionLine(workspaceAlias),          mtimeMs: Date.parse("2026-01-01T00:00:00.000Z"),        });         const result = yield* runScan({          claudeHomePath,          codexHomePath,          importedWorkspaceRoots: [workspace],        });         expect(result.candidates[0]).toMatchObject({          path: workspace,          projectId: ProjectId.make("project-1"),          alreadyImported: true,          git: null,        });      }),    );     it.effect("matches a persisted project alias to a transcript realpath", () =>      Effect.gen(function* () {        const path = yield* Path.Path;        const fileSystem = yield* FileSystem.FileSystem;        const claudeHomePath = yield* makeTempDir("t3code-claude-home-");        const codexHomePath = yield* makeTempDir("t3code-codex-home-");        const workspace = yield* makeTempDir("t3code-workspace-");        const linkParent = yield* makeTempDir("t3code-scanner-links-");        const workspaceAlias = path.join(linkParent, "workspace-alias");        yield* fileSystem.symlink(workspace, workspaceAlias);         yield* writeTranscript({          filePath: path.join(claudeHomePath, "projects", "-slug", "a.jsonl"),          contents: claudeSessionLine(workspace),          mtimeMs: Date.parse("2026-01-01T00:00:00.000Z"),        });         const result = yield* runScan({          claudeHomePath,          codexHomePath,          importedWorkspaceRoots: [workspaceAlias],        });         expect(result.candidates[0]).toMatchObject({          path: workspaceAlias,          projectId: ProjectId.make("project-1"),          alreadyImported: true,          git: null,        });      }),    );     it.effect("merges case aliases and preserves the persisted project path", () =>      Effect.gen(function* () {        const path = yield* Path.Path;        const fileSystem = yield* FileSystem.FileSystem;        const claudeHomePath = yield* makeTempDir("t3code-claude-home-");        const codexHomePath = yield* makeTempDir("t3code-codex-home-");        const workspace = yield* makeTempDir("t3code-workspace-");        const workspaceAlias = path.join(          path.dirname(workspace),          path.basename(workspace).toUpperCase(),        );         yield* writeTranscript({          filePath: path.join(claudeHomePath, "projects", "-slug", "a.jsonl"),          contents: claudeSessionLine(workspaceAlias),          mtimeMs: Date.parse("2026-01-01T00:00:00.000Z"),        });        yield* writeTranscript({          filePath: path.join(codexHomePath, "sessions", "2026", "01", "02", "rollout-b.jsonl"),          contents: codexRolloutLine(workspace),          mtimeMs: Date.parse("2026-01-02T00:00:00.000Z"),        });         const simulatedFileSystem = FileSystem.FileSystem.of({          ...fileSystem,          stat: (filePath) => fileSystem.stat(filePath === workspaceAlias ? workspace : filePath),        });        const result = yield* runScan({          claudeHomePath,          codexHomePath,          importedWorkspaceRoots: [workspace],        }).pipe(Effect.provideService(FileSystem.FileSystem, simulatedFileSystem));         expect(result.candidates).toEqual([          {

Lines 1–500 of 3216.