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 { 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 { 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 { 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( type: T, payload: Extract["payload"], overrides: Partial> = {}, ): Extract { 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; } 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, ); }); it("removes stale project index entries when thread.created recreates a thread under a new project", () => { const originalProjectId = ProjectId.make("project-1"); const recreatedProjectId = ProjectId.make("project-2"); const threadId = ThreadId.make("thread-1"); const thread = makeThread({ id: threadId, projectId: originalProjectId, }); const state = withActiveEnvironmentState(localEnvironmentStateOf(makeState(thread)), { projectIds: [originalProjectId, recreatedProjectId], projectById: { [originalProjectId]: { id: originalProjectId, environmentId: localEnvironmentId, name: "Project 1", cwd: "/tmp/project-1", defaultModelSelection: { provider: "codex", model: DEFAULT_MODEL_BY_PROVIDER.codex, }, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", scripts: [], }, [recreatedProjectId]: { id: recreatedProjectId, environmentId: localEnvironmentId, name: "Project 2", cwd: "/tmp/project-2", 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("thread.created", { threadId, projectId: recreatedProjectId, title: "Recovered thread", modelSelection: { provider: "codex", model: DEFAULT_MODEL_BY_PROVIDER.codex, }, runtimeMode: DEFAULT_RUNTIME_MODE, interactionMode: DEFAULT_INTERACTION_MODE, branch: null, worktreePath: null, createdAt: "2026-02-27T00:00:01.000Z", updatedAt: "2026-02-27T00:00:01.000Z", }), localEnvironmentId, ); expect(threadsOf(next)).toHaveLength(1); expect(threadsOf(next)[0]?.projectId).toBe(recreatedProjectId); expect(localEnvironmentStateOf(next).threadIdsByProjectId[originalProjectId]).toBeUndefined(); expect(localEnvironmentStateOf(next).threadIdsByProjectId[recreatedProjectId]).toEqual([ threadId, ]); }); it("updates only the affected thread for message events", () => { const thread1 = makeThread({ id: ThreadId.make("thread-1"), messages: [ { id: MessageId.make("message-1"), role: "assistant", text: "hello", turnId: TurnId.make("turn-1"), createdAt: "2026-02-27T00:00:00.000Z", completedAt: "2026-02-27T00:00:00.000Z", streaming: false, }, ], }); const thread2 = makeThread({ id: ThreadId.make("thread-2") }); const baseState = makeState(thread1); const baseEnvironmentState = localEnvironmentStateOf(baseState); const state = withActiveEnvironmentState(baseEnvironmentState, { threadIds: [thread1.id, thread2.id], threadShellById: { ...baseEnvironmentState.threadShellById, [thread2.id]: { id: thread2.id, environmentId: thread2.environmentId, codexThreadId: thread2.codexThreadId, projectId: thread2.projectId, title: thread2.title, modelSelection: thread2.modelSelection, runtimeMode: thread2.runtimeMode, interactionMode: thread2.interactionMode, error: thread2.error, createdAt: thread2.createdAt, archivedAt: thread2.archivedAt, updatedAt: thread2.updatedAt, branch: thread2.branch, worktreePath: thread2.worktreePath, }, }, threadSessionById: { ...baseEnvironmentState.threadSessionById, [thread2.id]: thread2.session, }, threadTurnStateById: { ...baseEnvironmentState.threadTurnStateById, [thread2.id]: { latestTurn: thread2.latestTurn, }, }, messageIdsByThreadId: { ...baseEnvironmentState.messageIdsByThreadId, [thread2.id]: [], }, messageByThreadId: { ...baseEnvironmentState.messageByThreadId, [thread2.id]: {}, }, activityIdsByThreadId: { ...baseEnvironmentState.activityIdsByThreadId, [thread2.id]: [], }, activityByThreadId: { ...baseEnvironmentState.activityByThreadId, [thread2.id]: {}, }, proposedPlanIdsByThreadId: { ...baseEnvironmentState.proposedPlanIdsByThreadId, [thread2.id]: [], }, proposedPlanByThreadId: { ...baseEnvironmentState.proposedPlanByThreadId, [thread2.id]: {}, }, turnDiffIdsByThreadId: { ...baseEnvironmentState.turnDiffIdsByThreadId, [thread2.id]: [], }, turnDiffSummaryByThreadId: { ...baseEnvironmentState.turnDiffSummaryByThreadId, [thread2.id]: {}, }, sidebarThreadSummaryById: { ...baseEnvironmentState.sidebarThreadSummaryById, }, threadIdsByProjectId: { [thread1.projectId]: [thread1.id, thread2.id], }, }); const next = applyOrchestrationEvent( state, makeEvent("thread.message-sent", { threadId: thread1.id, messageId: MessageId.make("message-1"), role: "assistant", text: " world", turnId: TurnId.make("turn-1"), streaming: true, createdAt: "2026-02-27T00:00:01.000Z", updatedAt: "2026-02-27T00:00:01.000Z", }), localEnvironmentId, ); expect(threadsOf(next)[0]?.messages[0]?.text).toBe("hello world"); expect(threadsOf(next)[0]?.latestTurn?.state).toBe("running"); const nextEnvironmentState = next.environmentStateById[localEnvironmentId]; const previousEnvironmentState = state.environmentStateById[localEnvironmentId]; expect(nextEnvironmentState?.threadShellById[thread2.id]).toBe( previousEnvironmentState?.threadShellById[thread2.id], ); expect(nextEnvironmentState?.threadSessionById[thread2.id]).toBe( previousEnvironmentState?.threadSessionById[thread2.id], ); expect(nextEnvironmentState?.messageIdsByThreadId[thread2.id]).toBe( previousEnvironmentState?.messageIdsByThreadId[thread2.id], ); expect(nextEnvironmentState?.messageByThreadId[thread2.id]).toBe( previousEnvironmentState?.messageByThreadId[thread2.id], ); }); it("applies replay batches in sequence and updates session state", () => { const thread = makeThread({ latestTurn: { turnId: TurnId.make("turn-1"), state: "running", requestedAt: "2026-02-27T00:00:00.000Z", startedAt: "2026-02-27T00:00:00.000Z", completedAt: null, assistantMessageId: null, }, }); const state = makeState(thread); const next = applyOrchestrationEvents( state, [ makeEvent( "thread.session-set", { threadId: thread.id, session: { threadId: thread.id, status: "running", providerName: "codex", runtimeMode: "full-access", activeTurnId: TurnId.make("turn-1"), lastError: null, updatedAt: "2026-02-27T00:00:02.000Z", }, }, { sequence: 2 }, ), makeEvent( "thread.message-sent", { threadId: thread.id, messageId: MessageId.make("assistant-1"), role: "assistant", text: "done", turnId: TurnId.make("turn-1"), streaming: false, createdAt: "2026-02-27T00:00:03.000Z", updatedAt: "2026-02-27T00:00:03.000Z", }, { sequence: 3 }, ), ], localEnvironmentId, ); expect(threadsOf(next)[0]?.session?.status).toBe("running"); expect(threadsOf(next)[0]?.latestTurn?.state).toBe("completed"); expect(threadsOf(next)[0]?.messages).toHaveLength(1); }); it("does not regress latestTurn when an older turn diff completes late", () => { const state = makeState( makeThread({ latestTurn: { turnId: TurnId.make("turn-2"), state: "running", requestedAt: "2026-02-27T00:00:02.000Z", startedAt: "2026-02-27T00:00:03.000Z", completedAt: null, assistantMessageId: null, }, }), ); const next = applyOrchestrationEvent( state, makeEvent("thread.turn-diff-completed", { threadId: ThreadId.make("thread-1"), turnId: TurnId.make("turn-1"), checkpointTurnCount: 1, checkpointRef: CheckpointRef.make("checkpoint-1"), status: "ready", files: [], assistantMessageId: MessageId.make("assistant-1"), completedAt: "2026-02-27T00:00:04.000Z", }), localEnvironmentId, ); expect(threadsOf(next)[0]?.turnDiffSummaries).toHaveLength(1); expect(threadsOf(next)[0]?.latestTurn).toEqual(threadsOf(state)[0]?.latestTurn); }); it("rebinds live turn diffs to the authoritative assistant message when it arrives later", () => { const turnId = TurnId.make("turn-1"); const state = makeState( makeThread({ latestTurn: { turnId, state: "completed", requestedAt: "2026-02-27T00:00:00.000Z", startedAt: "2026-02-27T00:00:00.000Z", completedAt: "2026-02-27T00:00:02.000Z", assistantMessageId: MessageId.make("assistant:turn-1"), }, turnDiffSummaries: [ { turnId, completedAt: "2026-02-27T00:00:02.000Z", status: "ready", checkpointTurnCount: 1, checkpointRef: CheckpointRef.make("checkpoint-1"), assistantMessageId: MessageId.make("assistant:turn-1"), files: [{ path: "src/app.ts", additions: 1, deletions: 0 }], }, ], }), ); const next = applyOrchestrationEvent( state, makeEvent("thread.message-sent", { threadId: ThreadId.make("thread-1"), messageId: MessageId.make("assistant-real"), role: "assistant", text: "final answer", turnId, streaming: false, createdAt: "2026-02-27T00:00:03.000Z", updatedAt: "2026-02-27T00:00:03.000Z", }), localEnvironmentId, ); expect(threadsOf(next)[0]?.turnDiffSummaries[0]?.assistantMessageId).toBe( MessageId.make("assistant-real"), ); expect(threadsOf(next)[0]?.latestTurn?.assistantMessageId).toBe( MessageId.make("assistant-real"), ); }); it("reverts messages, plans, activities, and checkpoints by retained turns", () => { const state = makeState( makeThread({ messages: [ { id: MessageId.make("user-1"), role: "user", text: "first", turnId: TurnId.make("turn-1"), createdAt: "2026-02-27T00:00:00.000Z", completedAt: "2026-02-27T00:00:00.000Z", streaming: false, }, { id: MessageId.make("assistant-1"), role: "assistant", text: "first reply", turnId: TurnId.make("turn-1"), createdAt: "2026-02-27T00:00:01.000Z", completedAt: "2026-02-27T00:00:01.000Z", streaming: false, }, { id: MessageId.make("user-2"), role: "user", text: "second", turnId: TurnId.make("turn-2"), createdAt: "2026-02-27T00:00:02.000Z", completedAt: "2026-02-27T00:00:02.000Z", streaming: false, }, ], proposedPlans: [ { id: "plan-1", turnId: TurnId.make("turn-1"), planMarkdown: "plan 1", implementedAt: null, implementationThreadId: null, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", }, { id: "plan-2", turnId: TurnId.make("turn-2"), planMarkdown: "plan 2", implementedAt: null, implementationThreadId: null, createdAt: "2026-02-27T00:00:02.000Z", updatedAt: "2026-02-27T00:00:02.000Z", }, ], activities: [ { id: EventId.make("activity-1"), tone: "info", kind: "step", summary: "one", payload: {}, turnId: TurnId.make("turn-1"), createdAt: "2026-02-27T00:00:00.000Z", }, { id: EventId.make("activity-2"), tone: "info", kind: "step", summary: "two", payload: {}, turnId: TurnId.make("turn-2"), createdAt: "2026-02-27T00:00:02.000Z", }, ], turnDiffSummaries: [ { turnId: TurnId.make("turn-1"), completedAt: "2026-02-27T00:00:01.000Z", status: "ready", checkpointTurnCount: 1, checkpointRef: CheckpointRef.make("ref-1"), files: [], }, { turnId: TurnId.make("turn-2"), completedAt: "2026-02-27T00:00:03.000Z", status: "ready", checkpointTurnCount: 2, checkpointRef: CheckpointRef.make("ref-2"), files: [], }, ], }), ); const next = applyOrchestrationEvent( state, makeEvent("thread.reverted", { threadId: ThreadId.make("thread-1"), turnCount: 1, }), localEnvironmentId, ); expect(threadsOf(next)[0]?.messages.map((message) => message.id)).toEqual([ "user-1", "assistant-1", ]); expect(threadsOf(next)[0]?.proposedPlans.map((plan) => plan.id)).toEqual(["plan-1"]); expect(threadsOf(next)[0]?.activities.map((activity) => activity.id)).toEqual([ EventId.make("activity-1"), ]); expect(threadsOf(next)[0]?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual([ TurnId.make("turn-1"), ]); }); it("clears pending source proposed plans after revert before a new session-set event", () => { const thread = makeThread({ latestTurn: { turnId: TurnId.make("turn-2"), state: "completed", requestedAt: "2026-02-27T00:00:02.000Z", startedAt: "2026-02-27T00:00:02.000Z", completedAt: "2026-02-27T00:00:03.000Z", assistantMessageId: MessageId.make("assistant-2"), sourceProposedPlan: { threadId: ThreadId.make("thread-source"), planId: "plan-2" as never, }, }, pendingSourceProposedPlan: { threadId: ThreadId.make("thread-source"), planId: "plan-2" as never, }, turnDiffSummaries: [ { turnId: TurnId.make("turn-1"), completedAt: "2026-02-27T00:00:01.000Z", status: "ready", checkpointTurnCount: 1, checkpointRef: CheckpointRef.make("ref-1"), files: [], }, { turnId: TurnId.make("turn-2"), completedAt: "2026-02-27T00:00:03.000Z", status: "ready", checkpointTurnCount: 2, checkpointRef: CheckpointRef.make("ref-2"), files: [], }, ], }); const reverted = applyOrchestrationEvent( makeState(thread), makeEvent("thread.reverted", { threadId: thread.id, turnCount: 1, }), localEnvironmentId, ); expect(threadsOf(reverted)[0]?.pendingSourceProposedPlan).toBeUndefined(); const next = applyOrchestrationEvent( reverted, makeEvent("thread.session-set", { threadId: thread.id, session: { threadId: thread.id, status: "running", providerName: "codex", runtimeMode: "full-access", activeTurnId: TurnId.make("turn-3"), lastError: null, updatedAt: "2026-02-27T00:00:04.000Z", }, }), localEnvironmentId, ); expect(threadsOf(next)[0]?.latestTurn).toMatchObject({ turnId: TurnId.make("turn-3"), state: "running", }); expect(threadsOf(next)[0]?.latestTurn?.sourceProposedPlan).toBeUndefined(); }); });