949 lines · 33.9 KB · TypeScript
RawDownload
import {  type OrchestrationEvent,  type OrchestrationMessage,  type OrchestrationProposedPlan,  type ProviderKind,  ThreadId,  type OrchestrationReadModel,  type OrchestrationSession,  type OrchestrationCheckpointSummary,  type OrchestrationThread,  type OrchestrationSessionStatus,} from "@t3tools/contracts";import { resolveModelSlugForProvider } from "@t3tools/shared/model";import { create } from "zustand";import { type ChatMessage, type Project, type Thread } from "./types"; // ── State ──────────────────────────────────────────────────────────── export interface AppState {  projects: Project[];  threads: Thread[];  bootstrapComplete: boolean;} const initialState: AppState = {  projects: [],  threads: [],  bootstrapComplete: false,};const MAX_THREAD_MESSAGES = 2_000;const MAX_THREAD_CHECKPOINTS = 500;const MAX_THREAD_PROPOSED_PLANS = 200;const MAX_THREAD_ACTIVITIES = 500; // ── Pure helpers ────────────────────────────────────────────────────── function updateThread(  threads: Thread[],  threadId: ThreadId,  updater: (t: Thread) => Thread,): Thread[] {  let changed = false;  const next = threads.map((t) => {    if (t.id !== threadId) return t;    const updated = updater(t);    if (updated !== t) changed = true;    return updated;  });  return changed ? next : threads;} function updateProject(  projects: Project[],  projectId: Project["id"],  updater: (project: Project) => Project,): Project[] {  let changed = false;  const next = projects.map((project) => {    if (project.id !== projectId) {      return project;    }    const updated = updater(project);    if (updated !== project) {      changed = true;    }    return updated;  });  return changed ? next : projects;} 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): Thread["session"] {  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(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: toAttachmentPreviewUrl(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): Thread["proposedPlans"][number] {  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,): Thread["turnDiffSummaries"][number] {  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 mapThread(thread: OrchestrationThread): Thread {  return {    id: thread.id,    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(mapMessage),    proposedPlans: thread.proposedPlans.map(mapProposedPlan),    error: thread.session?.lastError ?? null,    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 mapProject(project: OrchestrationReadModel["projects"][number]): Project {  return {    id: project.id,    name: project.title,    cwd: project.workspaceRoot,    defaultModelSelection: project.defaultModelSelection      ? normalizeModelSelection(project.defaultModelSelection)      : null,    createdAt: project.createdAt,    updatedAt: project.updatedAt,    scripts: mapProjectScripts(project.scripts),  };} function checkpointStatusToLatestTurnState(status: "ready" | "missing" | "error") {  if (status === "error") {    return "error" as const;  }  if (status === "missing") {    return "interrupted" as const;  }  return "completed" as const;} function compareActivities(  left: Thread["activities"][number],  right: Thread["activities"][number],): number {  if (left.sequence !== undefined && right.sequence !== undefined) {    if (left.sequence !== right.sequence) {      return left.sequence - right.sequence;    }  } else if (left.sequence !== undefined) {    return 1;  } else if (right.sequence !== undefined) {    return -1;  }   return left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id);} function buildLatestTurn(params: {  previous: Thread["latestTurn"];  turnId: NonNullable<Thread["latestTurn"]>["turnId"];  state: NonNullable<Thread["latestTurn"]>["state"];  requestedAt: string;  startedAt: string | null;  completedAt: string | null;  assistantMessageId: NonNullable<Thread["latestTurn"]>["assistantMessageId"];  sourceProposedPlan?: Thread["pendingSourceProposedPlan"];}): NonNullable<Thread["latestTurn"]> {  const resolvedPlan =    params.previous?.turnId === params.turnId      ? params.previous.sourceProposedPlan      : params.sourceProposedPlan;  return {    turnId: params.turnId,    state: params.state,    requestedAt: params.requestedAt,    startedAt: params.startedAt,    completedAt: params.completedAt,    assistantMessageId: params.assistantMessageId,    ...(resolvedPlan ? { sourceProposedPlan: resolvedPlan } : {}),  };} function rebindTurnDiffSummariesForAssistantMessage(  turnDiffSummaries: ReadonlyArray<Thread["turnDiffSummaries"][number]>,  turnId: Thread["turnDiffSummaries"][number]["turnId"],  assistantMessageId: NonNullable<Thread["latestTurn"]>["assistantMessageId"],): Thread["turnDiffSummaries"] {  let changed = false;  const nextSummaries = turnDiffSummaries.map((summary) => {    if (summary.turnId !== turnId || summary.assistantMessageId === assistantMessageId) {      return summary;    }    changed = true;    return {      ...summary,      assistantMessageId: assistantMessageId ?? undefined,    };  });  return changed ? nextSummaries : [...turnDiffSummaries];} function retainThreadMessagesAfterRevert(  messages: ReadonlyArray<ChatMessage>,  retainedTurnIds: ReadonlySet<string>,  turnCount: number,): ChatMessage[] {  const retainedMessageIds = new Set<string>();  for (const message of messages) {    if (message.role === "system") {      retainedMessageIds.add(message.id);      continue;    }    if (      message.turnId !== undefined &&      message.turnId !== null &&      retainedTurnIds.has(message.turnId)    ) {      retainedMessageIds.add(message.id);    }  }   const retainedUserCount = messages.filter(    (message) => message.role === "user" && retainedMessageIds.has(message.id),  ).length;  const missingUserCount = Math.max(0, turnCount - retainedUserCount);  if (missingUserCount > 0) {    const fallbackUserMessages = messages      .filter(        (message) =>          message.role === "user" &&          !retainedMessageIds.has(message.id) &&          (message.turnId === undefined ||            message.turnId === null ||            retainedTurnIds.has(message.turnId)),      )      .toSorted(        (left, right) =>          left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id),      )      .slice(0, missingUserCount);    for (const message of fallbackUserMessages) {      retainedMessageIds.add(message.id);    }  }   const retainedAssistantCount = messages.filter(    (message) => message.role === "assistant" && retainedMessageIds.has(message.id),  ).length;  const missingAssistantCount = Math.max(0, turnCount - retainedAssistantCount);  if (missingAssistantCount > 0) {    const fallbackAssistantMessages = messages      .filter(        (message) =>          message.role === "assistant" &&          !retainedMessageIds.has(message.id) &&          (message.turnId === undefined ||            message.turnId === null ||            retainedTurnIds.has(message.turnId)),      )      .toSorted(        (left, right) =>          left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id),      )      .slice(0, missingAssistantCount);    for (const message of fallbackAssistantMessages) {      retainedMessageIds.add(message.id);    }  }   return messages.filter((message) => retainedMessageIds.has(message.id));} function retainThreadActivitiesAfterRevert(  activities: ReadonlyArray<Thread["activities"][number]>,  retainedTurnIds: ReadonlySet<string>,): Thread["activities"] {  return activities.filter(    (activity) => activity.turnId === null || retainedTurnIds.has(activity.turnId),  );} function retainThreadProposedPlansAfterRevert(  proposedPlans: ReadonlyArray<Thread["proposedPlans"][number]>,  retainedTurnIds: ReadonlySet<string>,): Thread["proposedPlans"] {  return proposedPlans.filter(    (proposedPlan) => proposedPlan.turnId === null || retainedTurnIds.has(proposedPlan.turnId),  );} function toLegacySessionStatus(  status: OrchestrationSessionStatus,): "connecting" | "ready" | "running" | "error" | "closed" {  switch (status) {    case "starting":      return "connecting";    case "running":      return "running";    case "error":      return "error";    case "ready":    case "interrupted":      return "ready";    case "idle":    case "stopped":      return "closed";  }} function toLegacyProvider(providerName: string | null): ProviderKind {  if (providerName === "codex" || providerName === "claudeAgent") {    return providerName;  }  return "codex";} function resolveWsHttpOrigin(): string {  if (typeof window === "undefined") return "";  const bridgeWsUrl = window.desktopBridge?.getWsUrl?.();  const envWsUrl = import.meta.env.VITE_WS_URL as string | undefined;  const wsCandidate =    typeof bridgeWsUrl === "string" && bridgeWsUrl.length > 0      ? bridgeWsUrl      : typeof envWsUrl === "string" && envWsUrl.length > 0        ? envWsUrl        : null;  if (!wsCandidate) return window.location.origin;  try {    const wsUrl = new URL(wsCandidate);    const protocol =      wsUrl.protocol === "wss:" ? "https:" : wsUrl.protocol === "ws:" ? "http:" : wsUrl.protocol;    return `${protocol}//${wsUrl.host}`;  } catch {    return window.location.origin;  }} function toAttachmentPreviewUrl(rawUrl: string): string {  if (rawUrl.startsWith("/")) {    return `${resolveWsHttpOrigin()}${rawUrl}`;  }  return rawUrl;} function attachmentPreviewRoutePath(attachmentId: string): string {  return `/attachments/${encodeURIComponent(attachmentId)}`;} // ── Pure state transition functions ──────────────────────────────────── export function syncServerReadModel(state: AppState, readModel: OrchestrationReadModel): AppState {  const projects = readModel.projects    .filter((project) => project.deletedAt === null)    .map(mapProject);  const threads = readModel.threads.filter((thread) => thread.deletedAt === null).map(mapThread);  return {    ...state,    projects,    threads,    bootstrapComplete: true,  };} export function applyOrchestrationEvent(state: AppState, event: OrchestrationEvent): AppState {  switch (event.type) {    case "project.created": {      const existingIndex = state.projects.findIndex(        (project) =>          project.id === event.payload.projectId || project.cwd === event.payload.workspaceRoot,      );      const nextProject = mapProject({        id: event.payload.projectId,        title: event.payload.title,        workspaceRoot: event.payload.workspaceRoot,        defaultModelSelection: event.payload.defaultModelSelection,        scripts: event.payload.scripts,        createdAt: event.payload.createdAt,        updatedAt: event.payload.updatedAt,        deletedAt: null,      });      const projects =        existingIndex >= 0          ? state.projects.map((project, index) =>              index === existingIndex ? nextProject : project,            )          : [...state.projects, nextProject];      return { ...state, projects };    }     case "project.meta-updated": {      const projects = updateProject(state.projects, event.payload.projectId, (project) => ({        ...project,        ...(event.payload.title !== undefined ? { name: event.payload.title } : {}),        ...(event.payload.workspaceRoot !== undefined ? { cwd: event.payload.workspaceRoot } : {}),        ...(event.payload.defaultModelSelection !== undefined          ? {              defaultModelSelection: event.payload.defaultModelSelection                ? normalizeModelSelection(event.payload.defaultModelSelection)                : null,            }          : {}),        ...(event.payload.scripts !== undefined          ? { scripts: mapProjectScripts(event.payload.scripts) }          : {}),        updatedAt: event.payload.updatedAt,      }));      return projects === state.projects ? state : { ...state, projects };    }     case "project.deleted": {      const projects = state.projects.filter((project) => project.id !== event.payload.projectId);      return projects.length === state.projects.length ? state : { ...state, projects };    }     case "thread.created": {      const existing = state.threads.find((thread) => thread.id === event.payload.threadId);      const nextThread = mapThread({        id: event.payload.threadId,        projectId: event.payload.projectId,        title: event.payload.title,        modelSelection: event.payload.modelSelection,        runtimeMode: event.payload.runtimeMode,        interactionMode: event.payload.interactionMode,        branch: event.payload.branch,        worktreePath: event.payload.worktreePath,        latestTurn: null,        createdAt: event.payload.createdAt,        updatedAt: event.payload.updatedAt,        archivedAt: null,        deletedAt: null,        messages: [],        proposedPlans: [],        activities: [],        checkpoints: [],        session: null,      });      const threads = existing        ? state.threads.map((thread) => (thread.id === nextThread.id ? nextThread : thread))        : [...state.threads, nextThread];      return { ...state, threads };    }     case "thread.deleted": {      const threads = state.threads.filter((thread) => thread.id !== event.payload.threadId);      return threads.length === state.threads.length ? state : { ...state, threads };    }     case "thread.archived": {

Lines 1–500 of 949.