1580 lines · 52.0 KB · TypeScript
RawDownload
import { describe, expect, it } from "vite-plus/test"; import {  CheckpointRef,  CommandId,  EventId,  MessageId,  ProjectId,  ProviderInstanceId,  ThreadId,  TurnId,} from "@t3tools/contracts";import type { OrchestrationThread } from "@t3tools/contracts"; import { applyThreadDetailEvent } from "./threadReducer.ts"; const baseEventFields = {  eventId: EventId.make("event-1"),  commandId: null,  causationEventId: null,  correlationId: null,  metadata: {},} as const; const baseThread: OrchestrationThread = {  id: ThreadId.make("thread-1"),  projectId: ProjectId.make("project-1"),  title: "Test Thread",  modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" },  runtimeMode: "full-access",  interactionMode: "default",  branch: null,  worktreePath: null,  latestTurn: null,  createdAt: "2026-04-01T00:00:00.000Z",  updatedAt: "2026-04-01T00:00:00.000Z",  archivedAt: null,  settledOverride: null,  settledAt: null,  pullRequests: [],  deletedAt: null,  messages: [],  proposedPlans: [],  activities: [],  checkpoints: [],  session: null,}; describe("applyThreadDetailEvent", () => {  describe("project events", () => {    it("returns unchanged for project.created", () => {      const result = applyThreadDetailEvent(baseThread, {        ...baseEventFields,        sequence: 1,        occurredAt: "2026-04-01T01:00:00.000Z",        aggregateKind: "project",        aggregateId: ProjectId.make("project-1"),        type: "project.created",        payload: {          projectId: ProjectId.make("project-1"),          title: "T3 Code",          workspaceRoot: "/repo",          repositoryIdentity: null,          defaultModelSelection: null,          scripts: [],          createdAt: "2026-04-01T01:00:00.000Z",          updatedAt: "2026-04-01T01:00:00.000Z",          deletedAt: null,        },      } as any);      expect(result.kind).toBe("unchanged");    });  });   describe("thread.created", () => {    it("creates a fresh thread", () => {      const result = applyThreadDetailEvent(baseThread, {        ...baseEventFields,        sequence: 1,        occurredAt: "2026-04-01T01:00:00.000Z",        aggregateKind: "thread",        aggregateId: ThreadId.make("thread-2"),        type: "thread.created",        payload: {          threadId: ThreadId.make("thread-2"),          projectId: ProjectId.make("project-1"),          title: "New Thread",          modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" },          runtimeMode: "full-access",          interactionMode: "default",          branch: "main",          worktreePath: null,          createdAt: "2026-04-01T01:00:00.000Z",          updatedAt: "2026-04-01T01:00:00.000Z",        },      });       expect(result.kind).toBe("updated");      if (result.kind === "updated") {        expect(result.thread.id).toBe("thread-2");        expect(result.thread.title).toBe("New Thread");        expect(result.thread.branch).toBe("main");        expect(result.thread.messages).toEqual([]);        expect(result.thread.session).toBeNull();      }    });  });   describe("thread.deleted", () => {    it("returns deleted signal", () => {      const result = applyThreadDetailEvent(baseThread, {        ...baseEventFields,        sequence: 2,        occurredAt: "2026-04-01T02:00:00.000Z",        aggregateKind: "thread",        aggregateId: ThreadId.make("thread-1"),        type: "thread.deleted",        payload: {          threadId: ThreadId.make("thread-1"),          deletedAt: "2026-04-01T02:00:00.000Z",        },      });      expect(result.kind).toBe("deleted");    });  });   describe("thread.archived / thread.unarchived", () => {    it("sets archivedAt and clears title regeneration", () => {      const regeneratingThread: OrchestrationThread = {        ...baseThread,        titleRegeneration: {          requestId: CommandId.make("regenerate-title"),          startedAt: "2026-04-01T02:00:00.000Z",        },      };      const result = applyThreadDetailEvent(regeneratingThread, {        ...baseEventFields,        sequence: 3,        occurredAt: "2026-04-01T03:00:00.000Z",        aggregateKind: "thread",        aggregateId: ThreadId.make("thread-1"),        type: "thread.archived",        payload: {          threadId: ThreadId.make("thread-1"),          archivedAt: "2026-04-01T03:00:00.000Z",          updatedAt: "2026-04-01T03:00:00.000Z",        },      });       expect(result.kind).toBe("updated");      if (result.kind === "updated") {        expect(result.thread.archivedAt).toBe("2026-04-01T03:00:00.000Z");        expect(result.thread.titleRegeneration).toBeNull();      }    });     it("clears archivedAt", () => {      const archivedThread = { ...baseThread, archivedAt: "2026-04-01T03:00:00.000Z" };      const result = applyThreadDetailEvent(archivedThread, {        ...baseEventFields,        sequence: 4,        occurredAt: "2026-04-01T04:00:00.000Z",        aggregateKind: "thread",        aggregateId: ThreadId.make("thread-1"),        type: "thread.unarchived",        payload: {          threadId: ThreadId.make("thread-1"),          updatedAt: "2026-04-01T04:00:00.000Z",        },      });       expect(result.kind).toBe("updated");      if (result.kind === "updated") {        expect(result.thread.archivedAt).toBeNull();      }    });  });   describe("thread.settled / thread.unsettled", () => {    it("sets the settled override and timestamp", () => {      const settledAt = "2026-04-01T05:00:00.000Z";      const result = applyThreadDetailEvent(        { ...baseThread, activeOrderKey: "m" },        {          ...baseEventFields,          sequence: 5,          occurredAt: settledAt,          aggregateKind: "thread",          aggregateId: ThreadId.make("thread-1"),          type: "thread.settled",          payload: {            threadId: ThreadId.make("thread-1"),            settledAt,            updatedAt: settledAt,          },        },      );       expect(result.kind).toBe("updated");      if (result.kind === "updated") {        expect(result.thread.settledOverride).toBe("settled");        expect(result.thread.settledAt).toBe(settledAt);        expect(result.thread.activeOrderKey).toBeNull();      }    });     it.each([      ["user", "active"],      ["activity", null],    ] as const)("unsettles for %s with override %s", (reason, settledOverride) => {      const settledThread: OrchestrationThread = {        ...baseThread,        settledOverride: "settled",        settledAt: "2026-04-01T05:00:00.000Z",      };      const updatedAt = "2026-04-01T06:00:00.000Z";      const result = applyThreadDetailEvent(settledThread, {        ...baseEventFields,        sequence: 6,        occurredAt: updatedAt,        aggregateKind: "thread",        aggregateId: ThreadId.make("thread-1"),        type: "thread.unsettled",        payload: {          threadId: ThreadId.make("thread-1"),          reason,          updatedAt,        },      });       expect(result.kind).toBe("updated");      if (result.kind === "updated") {        expect(result.thread.settledOverride).toBe(settledOverride);        expect(result.thread.settledAt).toBeNull();      }    });  });   describe("thread.pinned / thread.unpinned", () => {    it("sets pinnedAt", () => {      const pinnedAt = "2026-04-01T05:00:00.000Z";      const result = applyThreadDetailEvent(        { ...baseThread, activeOrderKey: "m" },        {          ...baseEventFields,          sequence: 5,          occurredAt: pinnedAt,          aggregateKind: "thread",          aggregateId: ThreadId.make("thread-1"),          type: "thread.pinned",          payload: {            threadId: ThreadId.make("thread-1"),            pinnedAt,            updatedAt: pinnedAt,          },        },      );       expect(result.kind).toBe("updated");      if (result.kind === "updated") {        expect(result.thread.pinnedAt).toBe(pinnedAt);        expect(result.thread.activeOrderKey).toBe("m");      }    });     it("clears pinnedAt", () => {      const pinnedThread: OrchestrationThread = {        ...baseThread,        pinnedAt: "2026-04-01T05:00:00.000Z",      };      const updatedAt = "2026-04-01T06:00:00.000Z";      const result = applyThreadDetailEvent(pinnedThread, {        ...baseEventFields,        sequence: 6,        occurredAt: updatedAt,        aggregateKind: "thread",        aggregateId: ThreadId.make("thread-1"),        type: "thread.unpinned",        payload: {          threadId: ThreadId.make("thread-1"),          updatedAt,        },      });       expect(result.kind).toBe("updated");      if (result.kind === "updated") {        expect(result.thread.pinnedAt).toBeNull();      }    });  });   describe("thread.meta-updated", () => {    it.each(["f", null] as const)(      "updates the active key to %s without activity",      (activeOrderKey) => {        const result = applyThreadDetailEvent(          { ...baseThread, activeOrderKey: "m" },          {            ...baseEventFields,            sequence: 5,            occurredAt: "2026-04-01T05:00:00.000Z",            aggregateKind: "thread",            aggregateId: baseThread.id,            type: "thread.meta-updated",            payload: {              threadId: baseThread.id,              activeOrderKey,              updatedAt: baseThread.updatedAt,            },          },        );        expect(result.kind).toBe("updated");        if (result.kind === "updated") {          expect(result.thread.activeOrderKey).toBe(activeOrderKey);          expect(result.thread.updatedAt).toBe(baseThread.updatedAt);        }      },    );     it("patches title and branch", () => {      const result = applyThreadDetailEvent(        { ...baseThread, activeOrderKey: "m" },        {          ...baseEventFields,          sequence: 5,          occurredAt: "2026-04-01T05:00:00.000Z",          aggregateKind: "thread",          aggregateId: ThreadId.make("thread-1"),          type: "thread.meta-updated",          payload: {            threadId: ThreadId.make("thread-1"),            title: "Updated Title",            branch: "feature/demo",            updatedAt: "2026-04-01T05:00:00.000Z",          },        },      );       expect(result.kind).toBe("updated");      if (result.kind === "updated") {        expect(result.thread.title).toBe("Updated Title");        expect(result.thread.branch).toBe("feature/demo");        expect(result.thread.activeOrderKey).toBe("m");        // Model selection should be unchanged since it wasn't in the payload        expect(result.thread.modelSelection).toEqual(baseThread.modelSelection);      }    });     it.each(["linkedPullRequest", "branchPullRequest"] as const)(      "sets and clears %s without changing the other link",      (field) => {        const linkedPullRequest = {          projectId: ProjectId.make("project-1"),          repository: "pingdotgg/t3code",          number: 42,          url: "https://github.com/pingdotgg/t3code/pull/42",        };        const otherField =          field === "linkedPullRequest" ? "branchPullRequest" : "linkedPullRequest";        const otherPullRequest = {          ...linkedPullRequest,          number: 43,          url: "https://github.com/pingdotgg/t3code/pull/43",        };        const linked = applyThreadDetailEvent(          { ...baseThread, [otherField]: otherPullRequest },          {            ...baseEventFields,            sequence: 5,            occurredAt: "2026-04-01T05:00:00.000Z",            aggregateKind: "thread",            aggregateId: ThreadId.make("thread-1"),            type: "thread.meta-updated",            payload: {              threadId: ThreadId.make("thread-1"),              [field]: linkedPullRequest,              updatedAt: "2026-04-01T05:00:00.000Z",            },          },        );         expect(linked.kind).toBe("updated");        if (linked.kind !== "updated") return;        expect(linked.thread[field]).toEqual(linkedPullRequest);        expect(linked.thread[otherField]).toEqual(otherPullRequest);         const cleared = applyThreadDetailEvent(linked.thread, {          ...baseEventFields,          sequence: 6,          occurredAt: "2026-04-01T06:00:00.000Z",          aggregateKind: "thread",          aggregateId: ThreadId.make("thread-1"),          type: "thread.meta-updated",          payload: {            threadId: ThreadId.make("thread-1"),            [field]: null,            updatedAt: "2026-04-01T06:00:00.000Z",          },        });         expect(cleared.kind).toBe("updated");        if (cleared.kind === "updated") {          expect(cleared.thread[field]).toBeNull();          expect(cleared.thread[otherField]).toEqual(otherPullRequest);        }      },    );  });   describe("thread pull request links", () => {    const link = {      host: "github.com",      repository: "pingdotgg/t3code",      number: 42,      url: "https://github.com/pingdotgg/t3code/pull/42",      source: "manual" as const,      linkedAt: "2026-04-01T05:00:00.000Z",      snapshot: null,      stack: null,    };    const key = { host: "github.com", repository: "pingdotgg/t3code", number: 42 };    const linkEvent = (sequence: number) =>      ({        ...baseEventFields,        sequence,        occurredAt: "2026-04-01T05:00:00.000Z",        aggregateKind: "thread",        aggregateId: ThreadId.make("thread-1"),        type: "thread.pull-request-linked",        payload: {          threadId: ThreadId.make("thread-1"),          link,          updatedAt: "2026-04-01T05:00:00.000Z",        },      }) as const;     it("links without inventing a legacy route and replaces by key", () => {      const linked = applyThreadDetailEvent(baseThread, linkEvent(5));      expect(linked.kind).toBe("updated");      if (linked.kind !== "updated") return;      expect(linked.thread.pullRequests).toEqual([link]);      expect(linked.thread.linkedPullRequest).toBeNull();       const relinked = applyThreadDetailEvent(linked.thread, {        ...linkEvent(6),        payload: {          threadId: ThreadId.make("thread-1"),          link: { ...link, host: "GitHub.com", source: "agent" },          updatedAt: "2026-04-01T06:00:00.000Z",        },      });      if (relinked.kind !== "updated") throw new Error("expected update");      expect(relinked.thread.pullRequests).toHaveLength(1);      expect(relinked.thread.pullRequests[0]?.source).toBe("agent");    });     it("syncs snapshot and stack onto the matching link only", () => {      const linked = applyThreadDetailEvent(baseThread, linkEvent(5));      if (linked.kind !== "updated") throw new Error("expected update");      const snapshot = {        state: "merged" as const,        title: "Ship it",        headBranch: "feature",        baseBranch: "main",        isDraft: false,        updatedAt: "2026-04-02T00:00:00.000Z",        syncedAt: "2026-04-02T00:01:00.000Z",      };      const synced = applyThreadDetailEvent(linked.thread, {        ...baseEventFields,        sequence: 6,        occurredAt: "2026-04-02T00:01:00.000Z",        aggregateKind: "thread",        aggregateId: ThreadId.make("thread-1"),        type: "thread.pull-request-synced",        payload: {          threadId: ThreadId.make("thread-1"),          ...key,          snapshot,          stack: null,          updatedAt: "2026-04-02T00:01:00.000Z",        },      });      if (synced.kind !== "updated") throw new Error("expected update");      expect(synced.thread.pullRequests[0]?.snapshot).toEqual(snapshot);       const unknown = applyThreadDetailEvent(synced.thread, {        ...baseEventFields,        sequence: 7,        occurredAt: "2026-04-02T00:02:00.000Z",        aggregateKind: "thread",        aggregateId: ThreadId.make("thread-1"),        type: "thread.pull-request-synced",        payload: {          threadId: ThreadId.make("thread-1"),          ...key,          number: 99,          snapshot,          stack: null,          updatedAt: "2026-04-02T00:02:00.000Z",

Lines 1–500 of 1580.