860 lines · 30.3 KB · TypeScript
RawDownload
import { pipe } from "effect/Function";import * as Arr from "effect/Array";import * as O from "effect/Order";import type {  MessageId,  OrchestrationCheckpointSummary,  OrchestrationEvent,  OrchestrationLatestTurn,  OrchestrationMessage,  OrchestrationSession,  OrchestrationThread,  OrchestrationThreadActivity,  ThreadPullRequestLink,  TurnId,} from "@t3tools/contracts";import { threadPullRequestKeysEqual } from "@t3tools/shared/threadPullRequests";import { isImportedAgentSessionMessageId } from "@t3tools/contracts";import { compareDateTimeStrings } from "@t3tools/shared/dateTime"; export type ThreadDetailReducerResult =  | { readonly kind: "updated"; readonly thread: OrchestrationThread }  | { readonly kind: "deleted" }  | { readonly kind: "unchanged" }; /** Keep only a legacy route supplied by the server; detail events cannot resolve project hosts. */function withPullRequests(  thread: OrchestrationThread,  pullRequests: ReadonlyArray<ThreadPullRequestLink>,  updatedAt: string,): ThreadDetailReducerResult {  return {    kind: "updated",    thread: {      ...thread,      pullRequests,      linkedPullRequest:        thread.linkedPullRequest &&        pullRequests.some(          (link) => link.source !== "stack-dismissed" && link.url === thread.linkedPullRequest?.url,        )          ? thread.linkedPullRequest          : null,      updatedAt,    },  };} const proposedPlanOrder = O.combine<OrchestrationThread["proposedPlans"][number]>(  O.mapInput(O.String, (p) => p.createdAt),  O.mapInput(O.String, (p) => p.id),); const checkpointOrder = O.mapInput(  O.Number,  (cp: OrchestrationThread["checkpoints"][number]) =>    cp.checkpointTurnCount ?? Number.MAX_SAFE_INTEGER,); const activityOrder = O.combineAll<OrchestrationThreadActivity>([  O.mapInput(O.Number, (a) => a.sequence ?? Number.MAX_SAFE_INTEGER),  O.mapInput(O.String, (a) => a.createdAt),  O.mapInput(O.String, (a) => a.id),]); // Per-array id index so the streaming append path can reject a re-delivered// id without rescanning the history. Only arrays this reducer produced are// indexed: presence also proves the array is activityOrder-sorted, which// snapshot-loaded arrays (DB order, null sequences first) are not.const activityIdIndex = new WeakMap<  ReadonlyArray<OrchestrationThreadActivity>,  Set<OrchestrationThreadActivity["id"]>>(); /** * Matches the validity rule in `deriveLatestContextWindowSnapshot` (and the * server's snapshot-side `dropStaleContextWindowActivities`): rows without a * finite, non-negative `usedTokens` are skipped during the consumer's backward * walk, so they must not replace an earlier resolvable row here. */function isResolvableContextWindowActivity(activity: OrchestrationThreadActivity): boolean {  if (activity.kind !== "context-window.updated") {    return false;  }  const payload =    activity.payload && typeof activity.payload === "object"      ? (activity.payload as Record<string, unknown>)      : null;  const usedTokens = payload?.usedTokens;  return typeof usedTokens === "number" && Number.isFinite(usedTokens) && usedTokens >= 0;} /** * Apply a single orchestration event to an `OrchestrationThread`, returning * the updated thread, a deletion signal, or an "unchanged" marker when the * event doesn't affect this thread. * * This is a pure reducer operating on contract types. UI-specific mapping * (e.g. resolving attachment preview URLs, normalising model slugs, adding * scoped fields like `environmentId`) is the caller's responsibility. */export function applyThreadDetailEvent(  thread: OrchestrationThread,  event: OrchestrationEvent,): ThreadDetailReducerResult {  switch (event.type) {    // ── Project events (irrelevant to thread detail) ────────────────    case "project.created":    case "project.meta-updated":    case "project.deleted":      return { kind: "unchanged" };     // ── Thread lifecycle ────────────────────────────────────────────    case "thread.created":      return {        kind: "updated",        thread: {          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,          branchPullRequest: null,          latestTurn: null,          createdAt: event.payload.createdAt,          updatedAt: event.payload.updatedAt,          archivedAt: null,          settledOverride: null,          settledAt: null,          unsettledAt: null,          activeOrderKey: null,          snoozedUntil: null,          snoozedAt: null,          deletedAt: null,          pullRequests: [],          messages: [],          proposedPlans: [],          activities: [],          checkpoints: [],          session: null,        },      };     case "thread.deleted":      return { kind: "deleted" };     case "thread.archived":      return {        kind: "updated",        thread: {          ...thread,          archivedAt: event.payload.archivedAt,          titleRegeneration: null,          updatedAt: event.payload.updatedAt,        },      };     case "thread.unarchived":      return {        kind: "updated",        thread: { ...thread, archivedAt: null, updatedAt: event.payload.updatedAt },      };     case "thread.settled":      return {        kind: "updated",        thread: {          ...thread,          settledOverride: "settled",          settledAt: event.payload.settledAt,          unsettledAt: null,          activeOrderKey: null,          updatedAt: event.payload.updatedAt,        },      };     case "thread.unsettled":      return {        kind: "updated",        thread: {          ...thread,          settledOverride: event.payload.reason === "user" ? "active" : null,          settledAt: null,          // A thread already pinned active keeps its re-entry stamp: the          // activity reset that clears the pin must not reorder the list.          unsettledAt:            thread.settledOverride === "active"              ? (thread.unsettledAt ?? null)              : event.payload.updatedAt,          updatedAt: event.payload.updatedAt,        },      };     case "thread.snoozed":      return {        kind: "updated",        thread: {          ...thread,          snoozedUntil: event.payload.snoozedUntil,          snoozedAt: event.payload.snoozedAt,          updatedAt: event.payload.updatedAt,        },      };     case "thread.unsnoozed":      return {        kind: "updated",        thread: {          ...thread,          snoozedUntil: null,          snoozedAt: null,          updatedAt: event.payload.updatedAt,        },      };     case "thread.pinned":      return {        kind: "updated",        thread: {          ...thread,          pinnedAt: event.payload.pinnedAt,          ...(event.payload.pinOrderKey !== undefined            ? { pinOrderKey: event.payload.pinOrderKey }            : {}),          updatedAt: event.payload.updatedAt,        },      };     case "thread.unpinned":      return {        kind: "updated",        thread: {          ...thread,          pinnedAt: null,          pinOrderKey: null,          updatedAt: event.payload.updatedAt,        },      };     case "thread.pin-reordered":      return {        kind: "updated",        thread: {          ...thread,          pinOrderKey: event.payload.orderKey,          updatedAt: event.payload.updatedAt,        },      };     // ── Thread metadata ─────────────────────────────────────────────    case "thread.meta-updated":      return {        kind: "updated",        thread: {          ...thread,          ...(event.payload.title !== undefined ? { title: event.payload.title } : {}),          ...(event.payload.titleRegeneration !== undefined            ? { titleRegeneration: event.payload.titleRegeneration }            : {}),          ...(event.payload.modelSelection !== undefined            ? { modelSelection: event.payload.modelSelection }            : {}),          ...(event.payload.branch !== undefined ? { branch: event.payload.branch } : {}),          ...(event.payload.worktreePath !== undefined            ? { worktreePath: event.payload.worktreePath }            : {}),          ...(event.payload.linkedPullRequest !== undefined            ? { linkedPullRequest: event.payload.linkedPullRequest }            : {}),          ...(event.payload.branchPullRequest !== undefined            ? { branchPullRequest: event.payload.branchPullRequest }            : {}),          ...(event.payload.activeOrderKey !== undefined            ? { activeOrderKey: event.payload.activeOrderKey }            : {}),          updatedAt: event.payload.updatedAt,        },      };     case "thread.pull-request-linked": {      const link = event.payload.link;      const others = thread.pullRequests.filter(        (existing) => !threadPullRequestKeysEqual(existing, link),      );      return withPullRequests(thread, [...others, link], event.payload.updatedAt);    }     case "thread.pull-request-unlinked":      return withPullRequests(        thread,        thread.pullRequests.filter(          (existing) => !threadPullRequestKeysEqual(existing, event.payload),        ),        event.payload.updatedAt,      );     case "thread.pull-request-synced": {      if (        !thread.pullRequests.some((existing) => threadPullRequestKeysEqual(existing, event.payload))      ) {        return { kind: "unchanged" };      }      return withPullRequests(        thread,        thread.pullRequests.map((existing) =>          threadPullRequestKeysEqual(existing, event.payload)            ? { ...existing, snapshot: event.payload.snapshot, stack: event.payload.stack }            : existing,        ),        event.payload.updatedAt,      );    }     case "thread.runtime-mode-set":      return {        kind: "updated",        thread: {          ...thread,          runtimeMode: event.payload.runtimeMode,          updatedAt: event.payload.updatedAt,        },      };     case "thread.interaction-mode-set":      return {        kind: "updated",        thread: {          ...thread,          interactionMode: event.payload.interactionMode,          updatedAt: event.payload.updatedAt,        },      };     // ── Turn lifecycle ──────────────────────────────────────────────    case "thread.turn-start-requested":      return {        kind: "updated",        thread: {          ...thread,          ...(event.payload.modelSelection !== undefined            ? { modelSelection: event.payload.modelSelection }            : {}),          runtimeMode: event.payload.runtimeMode,          interactionMode: event.payload.interactionMode,          updatedAt: event.occurredAt,        },      };     case "thread.turn-interrupt-requested": {      if (event.payload.turnId === undefined) {        return { kind: "unchanged" };      }      const latestTurn = thread.latestTurn;      if (latestTurn === null || latestTurn.turnId !== event.payload.turnId) {        return { kind: "unchanged" };      }      return {        kind: "updated",        thread: {          ...thread,          latestTurn: {            ...latestTurn,            state: "interrupted",            startedAt: latestTurn.startedAt ?? event.payload.createdAt,            completedAt: latestTurn.completedAt ?? event.payload.createdAt,          },          updatedAt: event.occurredAt,        },      };    }     // ── Messages ────────────────────────────────────────────────────    case "thread.message-sent": {      const message: OrchestrationMessage = {        id: event.payload.messageId,        role: event.payload.role,        text: event.payload.text,        ...(event.payload.attachments !== undefined          ? { attachments: event.payload.attachments }          : {}),        turnId: event.payload.turnId,        streaming: event.payload.streaming,        createdAt: event.payload.createdAt,        updatedAt: event.payload.updatedAt,      };       const existingMessage = thread.messages.find((entry) => entry.id === message.id);      const messages = existingMessage        ? Arr.map(thread.messages, (entry) =>            entry.id !== message.id              ? entry              : {                  ...entry,                  text: message.streaming                    ? `${entry.text}${message.text}`                    : message.text.length > 0                      ? message.text                      : entry.text,                  streaming: message.streaming,                  ...(message.turnId !== undefined ? { turnId: message.turnId } : {}),                  ...(message.streaming ? {} : { updatedAt: message.updatedAt }),                  ...(message.attachments !== undefined                    ? { attachments: message.attachments }                    : {}),                },          )        : Arr.append(thread.messages, message);      // Update latestTurn for assistant messages bound to a turn. A completed      // assistant message only settles the turn once the session is no longer      // running it — providers may emit several assistant messages per turn      // (commentary between tool calls), and the turn must stay unsettled      // until the provider reports turn end. Streaming deltas recompute the      // same record, so the previous reference is kept when nothing changed.      const turnStillRunning =        event.payload.turnId !== null &&        thread.session?.status === "running" &&        thread.session.activeTurnId === event.payload.turnId;      const settlesTurn = !event.payload.streaming && !turnStillRunning;      const latestTurn = reuseLatestTurn(        thread.latestTurn,        event.payload.role === "assistant" &&          event.payload.turnId !== null &&          (thread.latestTurn === null || thread.latestTurn.turnId === event.payload.turnId)          ? {              turnId: event.payload.turnId,              state: settlesTurn                ? thread.latestTurn?.state === "interrupted"                  ? "interrupted"                  : thread.latestTurn?.state === "error"                    ? "error"                    : "completed"                : "running",              requestedAt:                thread.latestTurn?.turnId === event.payload.turnId                  ? thread.latestTurn.requestedAt                  : event.payload.createdAt,              startedAt:                thread.latestTurn?.turnId === event.payload.turnId                  ? (thread.latestTurn.startedAt ?? event.payload.createdAt)                  : event.payload.createdAt,              completedAt: settlesTurn                ? event.payload.updatedAt                : thread.latestTurn?.turnId === event.payload.turnId                  ? (thread.latestTurn.completedAt ?? null)                  : null,              assistantMessageId: event.payload.messageId,            }          : thread.latestTurn,      );       // Rebind checkpoint assistant message IDs for assistant messages. The      // helper hands back the same array when the entry is already bound.      const checkpoints =        event.payload.role === "assistant" && event.payload.turnId !== null          ? rebindCheckpointAssistantMessage(              thread.checkpoints,              event.payload.turnId,              event.payload.messageId,            )          : thread.checkpoints;       return {        kind: "updated",        thread: {          ...thread,          messages,          checkpoints,          latestTurn,          updatedAt: event.occurredAt,        },      };    }     // ── Session ─────────────────────────────────────────────────────    case "thread.session-set": {      // Leaving the "running" session status is the turn-end signal: settle a      // still-running latest turn so its duration reflects the whole turn.      const settledTurnState = settledTurnStateForSessionStatus(event.payload.session.status);      const latestTurn = reuseLatestTurn(        thread.latestTurn,        event.payload.session.status === "running" && event.payload.session.activeTurnId !== null          ? {              turnId: event.payload.session.activeTurnId,              state: "running",              requestedAt:                thread.latestTurn?.turnId === event.payload.session.activeTurnId                  ? thread.latestTurn.requestedAt                  : event.payload.session.updatedAt,              startedAt:                thread.latestTurn?.turnId === event.payload.session.activeTurnId                  ? (thread.latestTurn.startedAt ?? event.payload.session.updatedAt)                  : event.payload.session.updatedAt,              completedAt: null,              assistantMessageId:                thread.latestTurn?.turnId === event.payload.session.activeTurnId                  ? thread.latestTurn.assistantMessageId                  : null,            }

Lines 1–500 of 860.