1173 lines · 35.8 KB · TypeScript
RawDownload
import {  CommandId,  EventId,  ProjectId,  ProviderDriverKind,  ThreadId,  type OrchestrationEvent,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import { it as effectIt } from "@effect/vitest";import { describe, expect, it } from "vite-plus/test"; import { createEmptyReadModel, projectEvent } from "./projector.ts"; function makeEvent(input: {  sequence: number;  type: OrchestrationEvent["type"];  occurredAt: string;  aggregateKind: OrchestrationEvent["aggregateKind"];  aggregateId: string;  commandId: string | null;  payload: unknown;}): OrchestrationEvent {  return {    sequence: input.sequence,    eventId: EventId.make(`event-${input.sequence}`),    type: input.type,    aggregateKind: input.aggregateKind,    aggregateId:      input.aggregateKind === "project"        ? ProjectId.make(input.aggregateId)        : ThreadId.make(input.aggregateId),    occurredAt: input.occurredAt,    commandId: input.commandId === null ? null : CommandId.make(input.commandId),    causationEventId: null,    correlationId: null,    metadata: {},    payload: input.payload as never,  } as OrchestrationEvent;} describe("orchestration projector", () => {  it("applies thread.created events", async () => {    const now = "2026-01-01T00:00:00.000Z";    const model = createEmptyReadModel(now);     const next = await Effect.runPromise(      projectEvent(        model,        makeEvent({          sequence: 1,          type: "thread.created",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: now,          commandId: "cmd-thread-create",          payload: {            threadId: "thread-1",            projectId: "project-1",            title: "demo",            modelSelection: {              provider: ProviderDriverKind.make("codex"),              model: "gpt-5-codex",            },            runtimeMode: "full-access",            branch: null,            worktreePath: null,            createdAt: now,            updatedAt: now,          },        }),      ),    );     expect(next.snapshotSequence).toBe(1);    expect(next.threads).toEqual([      {        id: "thread-1",        projectId: "project-1",        title: "demo",        modelSelection: {          instanceId: "codex",          model: "gpt-5-codex",        },        runtimeMode: "full-access",        interactionMode: "default",        branch: null,        worktreePath: null,        pullRequests: [],        branchPullRequest: null,        latestTurn: null,        createdAt: now,        updatedAt: now,        archivedAt: null,        activeOrderKey: null,        settledOverride: null,        settledAt: null,        unsettledAt: null,        snoozedUntil: null,        snoozedAt: null,        deletedAt: null,        messages: [],        proposedPlans: [],        activities: [],        checkpoints: [],        session: null,      },    ]);  });   effectIt.effect("sets and clears branch pull requests without changing manual links", () =>    Effect.gen(function* () {      const now = "2026-01-01T00:00:00.000Z";      const eventFields = {        aggregateKind: "thread" as const,        aggregateId: "thread-1",        occurredAt: now,        commandId: null,      };      let model = yield* projectEvent(        {          ...createEmptyReadModel(now),          projects: [            {              id: ProjectId.make("project-1"),              title: "T3 Code",              workspaceRoot: "/repo",              defaultModelSelection: null,              scripts: [],              createdAt: now,              updatedAt: now,              deletedAt: null,              repositoryIdentity: {                canonicalKey: "github.com/pingdotgg/t3code",                provider: "github",                displayName: "pingdotgg/t3code",                locator: {                  source: "git-remote",                  remoteName: "origin",                  remoteUrl: "https://github.com/pingdotgg/t3code.git",                },              },            },          ],        },        makeEvent({          ...eventFields,          sequence: 1,          type: "thread.created",          payload: {            threadId: "thread-1",            projectId: "project-1",            title: "Pull request thread",            modelSelection: { provider: "codex", model: "gpt-5-codex" },            runtimeMode: "full-access",            branch: "feature",            worktreePath: null,            createdAt: now,            updatedAt: now,          },        }),      );      const linkedPullRequest = {        projectId: "project-1",        repository: "pingdotgg/t3code",        number: 42,        url: "https://github.com/pingdotgg/t3code/pull/42",      };      const branchPullRequest = {        ...linkedPullRequest,        number: 43,        url: "https://github.com/pingdotgg/t3code/pull/43",      };      const updates = [        { payload: { linkedPullRequest, branchPullRequest }, expected: branchPullRequest },        { payload: { title: "Renamed thread" }, expected: branchPullRequest },        { payload: { branchPullRequest: null }, expected: null },      ];       for (const [index, update] of updates.entries()) {        model = yield* projectEvent(          model,          makeEvent({            ...eventFields,            sequence: index + 2,            type: "thread.meta-updated",            payload: { threadId: "thread-1", updatedAt: now, ...update.payload },          }),        );        expect(model.threads[0]?.branchPullRequest).toEqual(update.expected);        expect(model.threads[0]?.linkedPullRequest).toEqual(linkedPullRequest);      }    }),  );   it("fails when event payload cannot be decoded by runtime schema", async () => {    const now = "2026-01-01T00:00:00.000Z";    const model = createEmptyReadModel(now);     await expect(      Effect.runPromise(        projectEvent(          model,          makeEvent({            sequence: 1,            type: "thread.created",            aggregateKind: "thread",            aggregateId: "thread-1",            occurredAt: now,            commandId: "cmd-invalid",            payload: {              // missing required threadId              projectId: "project-1",              title: "demo",              modelSelection: {                provider: ProviderDriverKind.make("codex"),                model: "gpt-5-codex",              },              branch: null,              worktreePath: null,              createdAt: now,              updatedAt: now,            },          }),        ),      ),    ).rejects.toBeDefined();  });   it("applies thread.archived and thread.unarchived events", async () => {    const now = "2026-01-01T00:00:00.000Z";    const later = "2026-01-01T00:00:01.000Z";    const created = await Effect.runPromise(      projectEvent(        createEmptyReadModel(now),        makeEvent({          sequence: 1,          type: "thread.created",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: now,          commandId: "cmd-thread-create",          payload: {            threadId: "thread-1",            projectId: "project-1",            title: "demo",            modelSelection: {              provider: ProviderDriverKind.make("codex"),              model: "gpt-5-codex",            },            runtimeMode: "full-access",            interactionMode: "default",            branch: null,            worktreePath: null,            createdAt: now,            updatedAt: now,          },        }),      ),    );     const archived = await Effect.runPromise(      projectEvent(        created,        makeEvent({          sequence: 2,          type: "thread.archived",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: later,          commandId: "cmd-thread-archive",          payload: {            threadId: "thread-1",            archivedAt: later,            updatedAt: later,          },        }),      ),    );    expect(archived.threads[0]?.archivedAt).toBe(later);     const unarchived = await Effect.runPromise(      projectEvent(        archived,        makeEvent({          sequence: 3,          type: "thread.unarchived",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: later,          commandId: "cmd-thread-unarchive",          payload: {            threadId: "thread-1",            updatedAt: later,          },        }),      ),    );    expect(unarchived.threads[0]?.archivedAt).toBeNull();  });   it("keeps projector forward-compatible for unhandled event types", async () => {    const now = "2026-01-01T00:00:00.000Z";    const model = createEmptyReadModel(now);     const next = await Effect.runPromise(      projectEvent(        model,        makeEvent({          sequence: 7,          type: "thread.turn-start-requested",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: "2026-01-01T00:00:00.000Z",          commandId: "cmd-unhandled",          payload: {            threadId: "thread-1",            messageId: "message-1",            runtimeMode: "approval-required",            createdAt: "2026-01-01T00:00:00.000Z",          },        }),      ),    );     expect(next.snapshotSequence).toBe(7);    expect(next.updatedAt).toBe("2026-01-01T00:00:00.000Z");    expect(next.threads).toEqual([]);  });   effectIt.effect.each([    ["ready", "completed"],    ["interrupted", "interrupted"],  ] as const)(    "preserves the turn state after a %s session captures its checkpoint",    ([status, state]) =>      Effect.gen(function* () {        const createdAt = "2026-02-23T08:00:00.000Z";        const startedAt = "2026-02-23T08:00:05.000Z";        const model = createEmptyReadModel(createdAt);         const afterCreate = yield* projectEvent(          model,          makeEvent({            sequence: 1,            type: "thread.created",            aggregateKind: "thread",            aggregateId: "thread-1",            occurredAt: createdAt,            commandId: "cmd-create",            payload: {              threadId: "thread-1",              projectId: "project-1",              title: "demo",              modelSelection: {                provider: ProviderDriverKind.make("codex"),                model: "gpt-5.3-codex",              },              runtimeMode: "full-access",              branch: null,              worktreePath: null,              createdAt,              updatedAt: createdAt,            },          }),        );         const settledAt = "2026-02-23T08:01:00.000Z";        const [afterRunning, afterReady] = yield* Effect.flatMap(          projectEvent(            afterCreate,            makeEvent({              sequence: 2,              type: "thread.session-set",              aggregateKind: "thread",              aggregateId: "thread-1",              occurredAt: startedAt,              commandId: "cmd-running",              payload: {                threadId: "thread-1",                session: {                  threadId: "thread-1",                  status: "running",                  providerName: "codex",                  providerSessionId: "session-1",                  providerThreadId: "provider-thread-1",                  runtimeMode: "approval-required",                  activeTurnId: "turn-1",                  lastError: null,                  updatedAt: startedAt,                },              },            }),          ),          (running) =>            Effect.map(              projectEvent(                running,                makeEvent({                  sequence: 3,                  type: "thread.session-set",                  aggregateKind: "thread",                  aggregateId: "thread-1",                  occurredAt: settledAt,                  commandId: "cmd-ready",                  payload: {                    threadId: "thread-1",                    session: {                      threadId: "thread-1",                      status,                      providerName: "codex",                      providerSessionId: "session-1",                      providerThreadId: "provider-thread-1",                      runtimeMode: "approval-required",                      activeTurnId: null,                      lastError: null,                      updatedAt: settledAt,                    },                  },                }),              ),              (ready) => [running, ready] as const,            ),        );         const thread = afterRunning.threads[0];        expect(thread?.latestTurn?.turnId).toBe("turn-1");        expect(thread?.session?.status).toBe("running");         // Leaving the "running" session status settles the running turn with the        // session timestamp as the turn end.        const settledThread = afterReady.threads[0];        expect(settledThread?.latestTurn?.turnId).toBe("turn-1");        expect(settledThread?.latestTurn?.state).toBe(state);        expect(settledThread?.latestTurn?.completedAt).toBe(settledAt);         const captured = yield* projectEvent(          afterReady,          makeEvent({            sequence: 4,            type: "thread.turn-diff-completed",            aggregateKind: "thread",            aggregateId: "thread-1",            occurredAt: settledAt,            commandId: "cmd-final-checkpoint",            payload: {              threadId: "thread-1",              turnId: "turn-1",              checkpointTurnCount: 1,              checkpointRef: "refs/t3/checkpoints/thread-1/turn/1",              status: "ready",              files: [],              assistantMessageId: "assistant:turn-1",              completedAt: settledAt,            },          }),        );        expect(captured.threads[0]?.latestTurn?.state).toBe(state);        expect(captured.threads[0]?.checkpoints[0]?.status).toBe("ready");      }),  );   effectIt.effect.each([null, "ready", "interrupted", "stopped"] as const)(    "replaces a missing checkpoint without inventing interruption for a %s session",    (sessionStatus) =>      Effect.gen(function* () {        const now = "2026-09-04T23:00:00.000Z";        const threadId = "thread-placeholder";        const event = (sequence: number, type: OrchestrationEvent["type"], payload: unknown) =>          makeEvent({            sequence,            type,            payload,            aggregateKind: "thread",            aggregateId: threadId,            occurredAt: now,            commandId: `placeholder-${sequence}`,          });        let model = yield* projectEvent(          createEmptyReadModel(now),          event(1, "thread.created", {            threadId,            projectId: "project-1",            title: "Placeholder",            modelSelection: { instanceId: "codex", model: "test" },            runtimeMode: "full-access",            interactionMode: "default",            branch: null,            worktreePath: null,            createdAt: now,            updatedAt: now,          }),        );        const checkpoint = {          threadId,          turnId: "turn-placeholder",          checkpointTurnCount: 1,          checkpointRef: "provider-diff:placeholder",          files: [],          assistantMessageId: "assistant:placeholder",

Lines 1–500 of 1173.