1790 lines · 59.8 KB · TypeScript
RawDownload
import type {  EnvironmentId,  MessageId,  OrchestrationCheckpointSummary,  OrchestrationEvent,  OrchestrationMessage,  OrchestrationProposedPlan,  OrchestrationReadModel,  OrchestrationSession,  OrchestrationSessionStatus,  OrchestrationThread,  OrchestrationThreadActivity,  ProjectId,  ProviderKind,  ScopedProjectRef,  ScopedThreadRef,  ThreadId,  TurnId,} from "@t3tools/contracts";import { resolveModelSlugForProvider } from "@t3tools/shared/model";import { create } from "zustand";import {  derivePendingApprovals,  derivePendingUserInputs,  findLatestProposedPlan,  hasActionableProposedPlan,} from "./session-logic";import {  type ChatMessage,  type Project,  type ProposedPlan,  type SidebarThreadSummary,  type Thread,  type ThreadSession,  type ThreadShell,  type ThreadTurnState,  type TurnDiffSummary,} from "./types";import { resolveEnvironmentHttpUrl } from "./environments/runtime";import { sanitizeThreadErrorMessage } from "./rpc/transportError"; export interface EnvironmentState {  projectIds: ProjectId[];  projectById: Record<ProjectId, Project>;  threadIds: ThreadId[];  threadIdsByProjectId: Record<ProjectId, ThreadId[]>;  threadShellById: Record<ThreadId, ThreadShell>;  threadSessionById: Record<ThreadId, ThreadSession | null>;  threadTurnStateById: Record<ThreadId, ThreadTurnState>;  messageIdsByThreadId: Record<ThreadId, MessageId[]>;  messageByThreadId: Record<ThreadId, Record<MessageId, ChatMessage>>;  activityIdsByThreadId: Record<ThreadId, string[]>;  activityByThreadId: Record<ThreadId, Record<string, OrchestrationThreadActivity>>;  proposedPlanIdsByThreadId: Record<ThreadId, string[]>;  proposedPlanByThreadId: Record<ThreadId, Record<string, ProposedPlan>>;  turnDiffIdsByThreadId: Record<ThreadId, TurnId[]>;  turnDiffSummaryByThreadId: Record<ThreadId, Record<TurnId, TurnDiffSummary>>;  sidebarThreadSummaryById: Record<ThreadId, SidebarThreadSummary>;  bootstrapComplete: boolean;} export interface AppState {  activeEnvironmentId: EnvironmentId | null;  environmentStateById: Record<string, EnvironmentState>;} const initialEnvironmentState: EnvironmentState = {  projectIds: [],  projectById: {},  threadIds: [],  threadIdsByProjectId: {},  threadShellById: {},  threadSessionById: {},  threadTurnStateById: {},  messageIdsByThreadId: {},  messageByThreadId: {},  activityIdsByThreadId: {},  activityByThreadId: {},  proposedPlanIdsByThreadId: {},  proposedPlanByThreadId: {},  turnDiffIdsByThreadId: {},  turnDiffSummaryByThreadId: {},  sidebarThreadSummaryById: {},  bootstrapComplete: false,}; const initialState: AppState = {  activeEnvironmentId: null,  environmentStateById: {},}; const MAX_THREAD_MESSAGES = 2_000;const MAX_THREAD_CHECKPOINTS = 500;const MAX_THREAD_PROPOSED_PLANS = 200;const MAX_THREAD_ACTIVITIES = 500;const EMPTY_THREAD_IDS: ThreadId[] = [];const EMPTY_MESSAGE_IDS: MessageId[] = [];const EMPTY_ACTIVITY_IDS: string[] = [];const EMPTY_PROPOSED_PLAN_IDS: string[] = [];const EMPTY_TURN_IDS: TurnId[] = [];const EMPTY_MESSAGES: ChatMessage[] = [];const EMPTY_ACTIVITIES: OrchestrationThreadActivity[] = [];const EMPTY_PROPOSED_PLANS: ProposedPlan[] = [];const EMPTY_TURN_DIFF_SUMMARIES: TurnDiffSummary[] = [];const EMPTY_MESSAGE_MAP: Record<MessageId, ChatMessage> = {};const EMPTY_ACTIVITY_MAP: Record<string, OrchestrationThreadActivity> = {};const EMPTY_PROPOSED_PLAN_MAP: Record<string, ProposedPlan> = {};const EMPTY_TURN_DIFF_MAP: Record<TurnId, TurnDiffSummary> = {};const EMPTY_THREAD_TURN_STATE: ThreadTurnState = Object.freeze({ latestTurn: null }); function arraysEqual<T>(left: readonly T[], right: readonly T[]): boolean {  return left.length === right.length && left.every((value, index) => value === right[index]);} function normalizeModelSelection<T extends { provider: "codex" | "claudeAgent"; model: string }>(  selection: T,): T {  return {    ...selection,    model: resolveModelSlugForProvider(selection.provider, selection.model),  };} function mapProjectScripts(scripts: ReadonlyArray<Project["scripts"][number]>): Project["scripts"] {  return scripts.map((script) => ({ ...script }));} function mapSession(session: OrchestrationSession): ThreadSession {  return {    provider: toLegacyProvider(session.providerName),    status: toLegacySessionStatus(session.status),    orchestrationStatus: session.status,    activeTurnId: session.activeTurnId ?? undefined,    createdAt: session.updatedAt,    updatedAt: session.updatedAt,    ...(session.lastError ? { lastError: session.lastError } : {}),  };} function mapMessage(environmentId: EnvironmentId, message: OrchestrationMessage): ChatMessage {  const attachments = message.attachments?.map((attachment) => ({    type: "image" as const,    id: attachment.id,    name: attachment.name,    mimeType: attachment.mimeType,    sizeBytes: attachment.sizeBytes,    previewUrl: resolveEnvironmentHttpUrl({      environmentId,      pathname: attachmentPreviewRoutePath(attachment.id),    }),  }));   return {    id: message.id,    role: message.role,    text: message.text,    turnId: message.turnId,    createdAt: message.createdAt,    streaming: message.streaming,    ...(message.streaming ? {} : { completedAt: message.updatedAt }),    ...(attachments && attachments.length > 0 ? { attachments } : {}),  };} function mapProposedPlan(proposedPlan: OrchestrationProposedPlan): ProposedPlan {  return {    id: proposedPlan.id,    turnId: proposedPlan.turnId,    planMarkdown: proposedPlan.planMarkdown,    implementedAt: proposedPlan.implementedAt,    implementationThreadId: proposedPlan.implementationThreadId,    createdAt: proposedPlan.createdAt,    updatedAt: proposedPlan.updatedAt,  };} function mapTurnDiffSummary(checkpoint: OrchestrationCheckpointSummary): TurnDiffSummary {  return {    turnId: checkpoint.turnId,    completedAt: checkpoint.completedAt,    status: checkpoint.status,    assistantMessageId: checkpoint.assistantMessageId ?? undefined,    checkpointTurnCount: checkpoint.checkpointTurnCount,    checkpointRef: checkpoint.checkpointRef,    files: checkpoint.files.map((file) => ({ ...file })),  };} function mapProject(  project: OrchestrationReadModel["projects"][number],  environmentId: EnvironmentId,): Project {  return {    id: project.id,    environmentId,    name: project.title,    cwd: project.workspaceRoot,    repositoryIdentity: project.repositoryIdentity ?? null,    defaultModelSelection: project.defaultModelSelection      ? normalizeModelSelection(project.defaultModelSelection)      : null,    createdAt: project.createdAt,    updatedAt: project.updatedAt,    scripts: mapProjectScripts(project.scripts),  };} function mapThread(thread: OrchestrationThread, environmentId: EnvironmentId): Thread {  return {    id: thread.id,    environmentId,    codexThreadId: null,    projectId: thread.projectId,    title: thread.title,    modelSelection: normalizeModelSelection(thread.modelSelection),    runtimeMode: thread.runtimeMode,    interactionMode: thread.interactionMode,    session: thread.session ? mapSession(thread.session) : null,    messages: thread.messages.map((message) => mapMessage(environmentId, message)),    proposedPlans: thread.proposedPlans.map(mapProposedPlan),    error: sanitizeThreadErrorMessage(thread.session?.lastError),    createdAt: thread.createdAt,    archivedAt: thread.archivedAt,    updatedAt: thread.updatedAt,    latestTurn: thread.latestTurn,    pendingSourceProposedPlan: thread.latestTurn?.sourceProposedPlan,    branch: thread.branch,    worktreePath: thread.worktreePath,    turnDiffSummaries: thread.checkpoints.map(mapTurnDiffSummary),    activities: thread.activities.map((activity) => ({ ...activity })),  };} function toThreadShell(thread: Thread): ThreadShell {  return {    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,  };} function toThreadTurnState(thread: Thread): ThreadTurnState {  return {    latestTurn: thread.latestTurn,    ...(thread.pendingSourceProposedPlan      ? { pendingSourceProposedPlan: thread.pendingSourceProposedPlan }      : {}),  };} function getLatestUserMessageAt(messages: ReadonlyArray<ChatMessage>): string | null {  let latestUserMessageAt: string | null = null;  for (const message of messages) {    if (message.role !== "user") {      continue;    }    if (latestUserMessageAt === null || message.createdAt > latestUserMessageAt) {      latestUserMessageAt = message.createdAt;    }  }  return latestUserMessageAt;} function buildSidebarThreadSummary(thread: Thread): SidebarThreadSummary {  return {    id: thread.id,    environmentId: thread.environmentId,    projectId: thread.projectId,    title: thread.title,    interactionMode: thread.interactionMode,    session: thread.session,    createdAt: thread.createdAt,    archivedAt: thread.archivedAt,    updatedAt: thread.updatedAt,    latestTurn: thread.latestTurn,    branch: thread.branch,    worktreePath: thread.worktreePath,    latestUserMessageAt: getLatestUserMessageAt(thread.messages),    hasPendingApprovals: derivePendingApprovals(thread.activities).length > 0,    hasPendingUserInput: derivePendingUserInputs(thread.activities).length > 0,    hasActionableProposedPlan: hasActionableProposedPlan(      findLatestProposedPlan(thread.proposedPlans, thread.latestTurn?.turnId ?? null),    ),  };} function sidebarThreadSummariesEqual(  left: SidebarThreadSummary | undefined,  right: SidebarThreadSummary,): boolean {  return (    left !== undefined &&    left.id === right.id &&    left.projectId === right.projectId &&    left.title === right.title &&    left.interactionMode === right.interactionMode &&    left.session === right.session &&    left.createdAt === right.createdAt &&    left.archivedAt === right.archivedAt &&    left.updatedAt === right.updatedAt &&    left.latestTurn === right.latestTurn &&    left.branch === right.branch &&    left.worktreePath === right.worktreePath &&    left.latestUserMessageAt === right.latestUserMessageAt &&    left.hasPendingApprovals === right.hasPendingApprovals &&    left.hasPendingUserInput === right.hasPendingUserInput &&    left.hasActionableProposedPlan === right.hasActionableProposedPlan  );} function threadShellsEqual(left: ThreadShell | undefined, right: ThreadShell): boolean {  return (    left !== undefined &&    left.id === right.id &&    left.environmentId === right.environmentId &&    left.codexThreadId === right.codexThreadId &&    left.projectId === right.projectId &&    left.title === right.title &&    left.modelSelection === right.modelSelection &&    left.runtimeMode === right.runtimeMode &&    left.interactionMode === right.interactionMode &&    left.error === right.error &&    left.createdAt === right.createdAt &&    left.archivedAt === right.archivedAt &&    left.updatedAt === right.updatedAt &&    left.branch === right.branch &&    left.worktreePath === right.worktreePath  );} function threadTurnStatesEqual(left: ThreadTurnState | undefined, right: ThreadTurnState): boolean {  return (    left !== undefined &&    left.latestTurn === right.latestTurn &&    left.pendingSourceProposedPlan === right.pendingSourceProposedPlan  );} function appendId<T extends string>(ids: readonly T[], id: T): T[] {  return ids.includes(id) ? [...ids] : [...ids, id];} function removeId<T extends string>(ids: readonly T[], id: T): T[] {  return ids.filter((value) => value !== id);} function buildMessageSlice(thread: Thread): {  ids: MessageId[];  byId: Record<MessageId, ChatMessage>;} {  return {    ids: thread.messages.map((message) => message.id),    byId: Object.fromEntries(      thread.messages.map((message) => [message.id, message] as const),    ) as Record<MessageId, ChatMessage>,  };} function buildActivitySlice(thread: Thread): {  ids: string[];  byId: Record<string, OrchestrationThreadActivity>;} {  return {    ids: thread.activities.map((activity) => activity.id),    byId: Object.fromEntries(      thread.activities.map((activity) => [activity.id, activity] as const),    ) as Record<string, OrchestrationThreadActivity>,  };} function buildProposedPlanSlice(thread: Thread): {  ids: string[];  byId: Record<string, ProposedPlan>;} {  return {    ids: thread.proposedPlans.map((plan) => plan.id),    byId: Object.fromEntries(      thread.proposedPlans.map((plan) => [plan.id, plan] as const),    ) as Record<string, ProposedPlan>,  };} function buildTurnDiffSlice(thread: Thread): {  ids: TurnId[];  byId: Record<TurnId, TurnDiffSummary>;} {  return {    ids: thread.turnDiffSummaries.map((summary) => summary.turnId),    byId: Object.fromEntries(      thread.turnDiffSummaries.map((summary) => [summary.turnId, summary] as const),    ) as Record<TurnId, TurnDiffSummary>,  };} function selectThreadMessages(state: EnvironmentState, threadId: ThreadId): ChatMessage[] {  const ids = state.messageIdsByThreadId[threadId] ?? EMPTY_MESSAGE_IDS;  const byId = state.messageByThreadId[threadId] ?? EMPTY_MESSAGE_MAP;  if (ids.length === 0) {    return EMPTY_MESSAGES;  }  return ids.flatMap((id) => {    const message = byId[id];    return message ? [message] : [];  });} function selectThreadActivities(  state: EnvironmentState,  threadId: ThreadId,): OrchestrationThreadActivity[] {  const ids = state.activityIdsByThreadId[threadId] ?? EMPTY_ACTIVITY_IDS;  const byId = state.activityByThreadId[threadId] ?? EMPTY_ACTIVITY_MAP;  if (ids.length === 0) {    return EMPTY_ACTIVITIES;  }  return ids.flatMap((id) => {    const activity = byId[id];    return activity ? [activity] : [];  });} function selectThreadProposedPlans(state: EnvironmentState, threadId: ThreadId): ProposedPlan[] {  const ids = state.proposedPlanIdsByThreadId[threadId] ?? EMPTY_PROPOSED_PLAN_IDS;  const byId = state.proposedPlanByThreadId[threadId] ?? EMPTY_PROPOSED_PLAN_MAP;  if (ids.length === 0) {    return EMPTY_PROPOSED_PLANS;  }  return ids.flatMap((id) => {    const plan = byId[id];    return plan ? [plan] : [];  });} function selectThreadTurnDiffSummaries(  state: EnvironmentState,  threadId: ThreadId,): TurnDiffSummary[] {  const ids = state.turnDiffIdsByThreadId[threadId] ?? EMPTY_TURN_IDS;  const byId = state.turnDiffSummaryByThreadId[threadId] ?? EMPTY_TURN_DIFF_MAP;  if (ids.length === 0) {    return EMPTY_TURN_DIFF_SUMMARIES;  }  return ids.flatMap((id) => {    const summary = byId[id];    return summary ? [summary] : [];  });} function getThread(state: EnvironmentState, threadId: ThreadId): Thread | undefined {  const shell = state.threadShellById[threadId];  if (!shell) {    return undefined;  }  const turnState = state.threadTurnStateById[threadId] ?? EMPTY_THREAD_TURN_STATE;  return {    ...shell,    session: state.threadSessionById[threadId] ?? null,    latestTurn: turnState.latestTurn,    pendingSourceProposedPlan: turnState.pendingSourceProposedPlan,    messages: selectThreadMessages(state, threadId),    activities: selectThreadActivities(state, threadId),    proposedPlans: selectThreadProposedPlans(state, threadId),    turnDiffSummaries: selectThreadTurnDiffSummaries(state, threadId),  };} function getProjects(state: EnvironmentState): Project[] {  return state.projectIds.flatMap((projectId) => {    const project = state.projectById[projectId];    return project ? [project] : [];  });} function getThreads(state: EnvironmentState): Thread[] {  return state.threadIds.flatMap((threadId) => {    const thread = getThread(state, threadId);    return thread ? [thread] : [];  });} function writeThreadState(  state: EnvironmentState,  nextThread: Thread,  previousThread?: Thread,): EnvironmentState {  const nextShell = toThreadShell(nextThread);  const nextTurnState = toThreadTurnState(nextThread);  const previousShell = state.threadShellById[nextThread.id];  const previousTurnState = state.threadTurnStateById[nextThread.id];

Lines 1–500 of 1790.