1017 lines · 31.2 KB · TypeScript
RawDownload
import { scopeThreadRef } from "@t3tools/client-runtime";import {  CheckpointRef,  DEFAULT_MODEL_BY_PROVIDER,  EnvironmentId,  EventId,  MessageId,  ProjectId,  ThreadId,  TurnId,  type OrchestrationEvent,} from "@t3tools/contracts";import { describe, expect, it } from "vitest"; import {  applyOrchestrationEvent,  applyOrchestrationEvents,  selectEnvironmentState,  selectProjectsAcrossEnvironments,  selectThreadByRef,  selectThreadExistsByRef,  setThreadBranch,  selectThreadsAcrossEnvironments,  type AppState,  type EnvironmentState,} from "./store";import { DEFAULT_INTERACTION_MODE, DEFAULT_RUNTIME_MODE, type Thread } from "./types"; const localEnvironmentId = EnvironmentId.make("environment-local");const remoteEnvironmentId = EnvironmentId.make("environment-remote"); function withActiveEnvironmentState(  environmentState: EnvironmentState,  overrides: Partial<AppState & EnvironmentState> = {},): AppState {  const {    activeEnvironmentId: overrideActiveEnvironmentId,    environmentStateById: overrideEnvironmentStateById,    ...environmentOverrides  } = overrides;  const activeEnvironmentId = overrideActiveEnvironmentId ?? localEnvironmentId;  const mergedEnvironmentState = {    ...environmentState,    ...environmentOverrides,  };  const environmentStateById =    overrideEnvironmentStateById ??    (activeEnvironmentId      ? {          [activeEnvironmentId]: mergedEnvironmentState,        }      : {});   return {    activeEnvironmentId,    environmentStateById,  };} function makeThread(overrides: Partial<Thread> = {}): Thread {  return {    id: ThreadId.make("thread-1"),    environmentId: localEnvironmentId,    codexThreadId: null,    projectId: ProjectId.make("project-1"),    title: "Thread",    modelSelection: {      provider: "codex",      model: "gpt-5-codex",    },    runtimeMode: DEFAULT_RUNTIME_MODE,    interactionMode: DEFAULT_INTERACTION_MODE,    session: null,    messages: [],    turnDiffSummaries: [],    activities: [],    proposedPlans: [],    error: null,    createdAt: "2026-02-13T00:00:00.000Z",    archivedAt: null,    latestTurn: null,    branch: null,    worktreePath: null,    ...overrides,  };} function makeState(thread: Thread): AppState {  const projectId = ProjectId.make("project-1");  const project = {    id: projectId,    environmentId: thread.environmentId,    name: "Project",    cwd: "/tmp/project",    defaultModelSelection: {      provider: "codex" as const,      model: "gpt-5-codex",    },    createdAt: "2026-02-13T00:00:00.000Z",    updatedAt: "2026-02-13T00:00:00.000Z",    scripts: [],  };  const threadIdsByProjectId: EnvironmentState["threadIdsByProjectId"] = {    [thread.projectId]: [thread.id],  };  const environmentState = {    projectIds: [projectId],    projectById: {      [projectId]: project,    },    threadIds: [thread.id],    threadIdsByProjectId,    threadShellById: {      [thread.id]: {        id: thread.id,        environmentId: thread.environmentId,        codexThreadId: thread.codexThreadId,        projectId: thread.projectId,        title: thread.title,        modelSelection: thread.modelSelection,        runtimeMode: thread.runtimeMode,        interactionMode: thread.interactionMode,        error: thread.error,        createdAt: thread.createdAt,        archivedAt: thread.archivedAt,        updatedAt: thread.updatedAt,        branch: thread.branch,        worktreePath: thread.worktreePath,      },    },    threadSessionById: {      [thread.id]: thread.session,    },    threadTurnStateById: {      [thread.id]: {        latestTurn: thread.latestTurn,        ...(thread.pendingSourceProposedPlan          ? { pendingSourceProposedPlan: thread.pendingSourceProposedPlan }          : {}),      },    },    messageIdsByThreadId: {      [thread.id]: thread.messages.map((message) => message.id),    },    messageByThreadId: {      [thread.id]: Object.fromEntries(        thread.messages.map((message) => [message.id, message] as const),      ) as EnvironmentState["messageByThreadId"][ThreadId],    },    activityIdsByThreadId: {      [thread.id]: thread.activities.map((activity) => activity.id),    },    activityByThreadId: {      [thread.id]: Object.fromEntries(        thread.activities.map((activity) => [activity.id, activity] as const),      ) as EnvironmentState["activityByThreadId"][ThreadId],    },    proposedPlanIdsByThreadId: {      [thread.id]: thread.proposedPlans.map((plan) => plan.id),    },    proposedPlanByThreadId: {      [thread.id]: Object.fromEntries(        thread.proposedPlans.map((plan) => [plan.id, plan] as const),      ) as EnvironmentState["proposedPlanByThreadId"][ThreadId],    },    turnDiffIdsByThreadId: {      [thread.id]: thread.turnDiffSummaries.map((summary) => summary.turnId),    },    turnDiffSummaryByThreadId: {      [thread.id]: Object.fromEntries(        thread.turnDiffSummaries.map((summary) => [summary.turnId, summary] as const),      ) as EnvironmentState["turnDiffSummaryByThreadId"][ThreadId],    },    sidebarThreadSummaryById: {},    bootstrapComplete: true,  };  return withActiveEnvironmentState(environmentState, {    activeEnvironmentId: thread.environmentId,  });} function makeEmptyState(overrides: Partial<AppState & EnvironmentState> = {}): AppState {  const environmentState: EnvironmentState = {    projectIds: [],    projectById: {},    threadIds: [],    threadIdsByProjectId: {},    threadShellById: {},    threadSessionById: {},    threadTurnStateById: {},    messageIdsByThreadId: {},    messageByThreadId: {},    activityIdsByThreadId: {},    activityByThreadId: {},    proposedPlanIdsByThreadId: {},    proposedPlanByThreadId: {},    turnDiffIdsByThreadId: {},    turnDiffSummaryByThreadId: {},    sidebarThreadSummaryById: {},    bootstrapComplete: true,  };  return withActiveEnvironmentState(environmentState, overrides);} function localEnvironmentStateOf(state: AppState): EnvironmentState {  return selectEnvironmentState(state, localEnvironmentId);} function environmentStateOf(state: AppState, environmentId: EnvironmentId): EnvironmentState {  return selectEnvironmentState(state, environmentId);} function projectsOf(state: AppState) {  return selectProjectsAcrossEnvironments(state);} function threadsOf(state: AppState) {  return selectThreadsAcrossEnvironments(state);} function makeEvent<T extends OrchestrationEvent["type"]>(  type: T,  payload: Extract<OrchestrationEvent, { type: T }>["payload"],  overrides: Partial<Extract<OrchestrationEvent, { type: T }>> = {},): Extract<OrchestrationEvent, { type: T }> {  const sequence = overrides.sequence ?? 1;  return {    sequence,    eventId: EventId.make(`event-${sequence}`),    aggregateKind: "thread",    aggregateId:      "threadId" in payload        ? payload.threadId        : "projectId" in payload          ? payload.projectId          : ProjectId.make("project-1"),    occurredAt: "2026-02-27T00:00:00.000Z",    commandId: null,    causationEventId: null,    correlationId: null,    metadata: {},    type,    payload,    ...overrides,  } as Extract<OrchestrationEvent, { type: T }>;} describe("thread selection memoization", () => {  it("returns stable thread references for repeated reads of the same state", () => {    const thread = makeThread({      messages: [        {          id: MessageId.make("message-1"),          role: "user",          text: "hello",          createdAt: "2026-02-13T00:01:00.000Z",          streaming: false,        },      ],      activities: [        {          id: EventId.make("activity-1"),          tone: "info",          kind: "step",          summary: "working",          payload: {},          turnId: TurnId.make("turn-1"),          createdAt: "2026-02-13T00:01:30.000Z",        },      ],      proposedPlans: [        {          id: "plan-1",          turnId: null,          planMarkdown: "plan",          implementedAt: null,          implementationThreadId: null,          createdAt: "2026-02-13T00:02:00.000Z",          updatedAt: "2026-02-13T00:02:00.000Z",        },      ],      turnDiffSummaries: [        {          turnId: TurnId.make("turn-1"),          completedAt: "2026-02-13T00:03:00.000Z",          files: [],        },      ],    });    const state = makeState(thread);    const ref = scopeThreadRef(thread.environmentId, thread.id);     const first = selectThreadByRef(state, ref);    const second = selectThreadByRef(state, ref);     expect(first).toBeDefined();    expect(second).toBe(first);    expect(second?.messages).toBe(first?.messages);    expect(second?.activities).toBe(first?.activities);    expect(second?.proposedPlans).toBe(first?.proposedPlans);    expect(second?.turnDiffSummaries).toBe(first?.turnDiffSummaries);  });   it("reuses the derived thread when the app state wrapper changes but thread data does not", () => {    const thread = makeThread({      messages: [        {          id: MessageId.make("message-1"),          role: "assistant",          text: "done",          createdAt: "2026-02-13T00:01:00.000Z",          streaming: false,        },      ],    });    const state = makeState(thread);    const ref = scopeThreadRef(thread.environmentId, thread.id);    const wrappedState: AppState = {      ...state,      environmentStateById: { ...state.environmentStateById },    };     const first = selectThreadByRef(state, ref);    const second = selectThreadByRef(wrappedState, ref);     expect(second).toBe(first);  });   it("updates the derived thread when the underlying thread data changes", () => {    const thread = makeThread();    const ref = scopeThreadRef(thread.environmentId, thread.id);    const firstState = makeState(thread);    const secondState = makeState({      ...thread,      messages: [        {          id: MessageId.make("message-2"),          role: "user",          text: "new",          createdAt: "2026-02-13T00:04:00.000Z",          streaming: false,        },      ],    });     const first = selectThreadByRef(firstState, ref);    const second = selectThreadByRef(secondState, ref);     expect(second).not.toBe(first);    expect(second?.messages).toHaveLength(1);    expect(second?.messages[0]?.text).toBe("new");  });   it("checks thread existence without materializing the full thread", () => {    const thread = makeThread();    const state = makeState(thread);    const ref = scopeThreadRef(thread.environmentId, thread.id);     expect(selectThreadExistsByRef(state, ref)).toBe(true);    expect(      selectThreadExistsByRef(        state,        scopeThreadRef(thread.environmentId, ThreadId.make("missing")),      ),    ).toBe(false);    expect(selectThreadExistsByRef(state, null)).toBe(false);  });}); describe("setThreadBranch", () => {  it("updates only the scoped thread environment", () => {    const sharedThreadId = ThreadId.make("thread-shared");    const localThread = makeThread({      id: sharedThreadId,      environmentId: localEnvironmentId,      branch: "local-branch",    });    const remoteThread = makeThread({      id: sharedThreadId,      environmentId: remoteEnvironmentId,      branch: "remote-branch",    });    const state: AppState = {      activeEnvironmentId: localEnvironmentId,      environmentStateById: {        [localEnvironmentId]: environmentStateOf(makeState(localThread), localEnvironmentId),        [remoteEnvironmentId]: environmentStateOf(makeState(remoteThread), remoteEnvironmentId),      },    };     const next = setThreadBranch(      state,      scopeThreadRef(remoteEnvironmentId, sharedThreadId),      "remote-next",      "/tmp/remote-worktree",    );     expect(      environmentStateOf(next, localEnvironmentId).threadShellById[sharedThreadId]?.branch,    ).toBe("local-branch");    expect(      environmentStateOf(next, remoteEnvironmentId).threadShellById[sharedThreadId]?.branch,    ).toBe("remote-next");    expect(      environmentStateOf(next, remoteEnvironmentId).threadShellById[sharedThreadId]?.worktreePath,    ).toBe("/tmp/remote-worktree");  });}); describe("incremental orchestration updates", () => {  it("does not mark bootstrap complete for incremental events", () => {    const state = withActiveEnvironmentState(localEnvironmentStateOf(makeState(makeThread())), {      bootstrapComplete: false,    });     const next = applyOrchestrationEvent(      state,      makeEvent("thread.meta-updated", {        threadId: ThreadId.make("thread-1"),        title: "Updated title",        updatedAt: "2026-02-27T00:00:01.000Z",      }),      localEnvironmentId,    );     expect(localEnvironmentStateOf(next).bootstrapComplete).toBe(false);  });   it("preserves state identity for no-op project and thread deletes", () => {    const thread = makeThread();    const state = makeState(thread);     const nextAfterProjectDelete = applyOrchestrationEvent(      state,      makeEvent("project.deleted", {        projectId: ProjectId.make("project-missing"),        deletedAt: "2026-02-27T00:00:01.000Z",      }),      localEnvironmentId,    );    const nextAfterThreadDelete = applyOrchestrationEvent(      state,      makeEvent("thread.deleted", {        threadId: ThreadId.make("thread-missing"),        deletedAt: "2026-02-27T00:00:01.000Z",      }),      localEnvironmentId,    );     expect(nextAfterProjectDelete).toBe(state);    expect(nextAfterThreadDelete).toBe(state);  });   it("reuses an existing project row when project.created arrives with a new id for the same cwd", () => {    const originalProjectId = ProjectId.make("project-1");    const recreatedProjectId = ProjectId.make("project-2");    const state: AppState = makeEmptyState({      projectIds: [originalProjectId],      projectById: {        [originalProjectId]: {          id: originalProjectId,          environmentId: localEnvironmentId,          name: "Project",          cwd: "/tmp/project",          defaultModelSelection: {            provider: "codex",            model: DEFAULT_MODEL_BY_PROVIDER.codex,          },          createdAt: "2026-02-27T00:00:00.000Z",          updatedAt: "2026-02-27T00:00:00.000Z",          scripts: [],        },      },    });     const next = applyOrchestrationEvent(      state,      makeEvent("project.created", {        projectId: recreatedProjectId,        title: "Project Recreated",        workspaceRoot: "/tmp/project",        defaultModelSelection: {          provider: "codex",          model: DEFAULT_MODEL_BY_PROVIDER.codex,        },        scripts: [],        createdAt: "2026-02-27T00:00:01.000Z",        updatedAt: "2026-02-27T00:00:01.000Z",      }),      localEnvironmentId,    );     expect(projectsOf(next)).toHaveLength(1);    expect(projectsOf(next)[0]?.id).toBe(recreatedProjectId);    expect(projectsOf(next)[0]?.cwd).toBe("/tmp/project");    expect(projectsOf(next)[0]?.name).toBe("Project Recreated");    expect(localEnvironmentStateOf(next).projectIds).toEqual([recreatedProjectId]);    expect(localEnvironmentStateOf(next).projectById[originalProjectId]).toBeUndefined();    expect(localEnvironmentStateOf(next).projectById[recreatedProjectId]?.id).toBe(      recreatedProjectId,

Lines 1–500 of 1017.