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. Load more
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",