1418 lines · 47.9 KB · TypeScript
RawDownload
import * as Equal from "effect/Equal";import { shallow } from "zustand/vanilla/shallow";import { renderCodexDirectivesForCopy } from "@t3tools/client-runtime/codex-markdown-directives";import { commandProgramName } from "@t3tools/client-runtime/work-log/command-label";import {  liveActivityToolStatus,  normalizeCompactToolLabel,  omitSupersededLifecycleMarkers,  resolveWorkEntryToolPresentation,  summarizeToolGroup,  toolGroupAction,  toolGroupSummaryKind,  type ToolGroupSummaryKind,} from "@t3tools/client-runtime/work-log/presentation";export {  normalizeCompactToolLabel,  toolGroupAction,} from "@t3tools/client-runtime/work-log/presentation";import {  formatDuration,  inferCheckpointTurnCountByTurnId,  isStreamingMessageTextUpdate,  workEntryDisplayIndicatesToolFailure,  workEntryIndicatesToolSuccess,  workEntryIndicatesToolNeutralStatus,  workLogEntryIsToolLike,  type TimelineEntry,  type WorkLogEntry,} from "../../session-logic";import { type ChatMessage, type ProposedPlan, type TurnDiffSummary } from "../../types";import { type MessageId, type OrchestrationLatestTurn, type TurnId } from "@t3tools/contracts";import { formatWorkspaceRelativePath } from "../../filePathDisplay"; const TIMELINE_MINIMAP_ITEM_SPACING = 8;export const TIMELINE_MINIMAP_MIN_ITEMS = 2;const TIMELINE_MINIMAP_MAX_HEIGHT_CSS = "calc(100vh - 18rem)";const TIMELINE_CONTENT_MAX_WIDTH = 768;const TIMELINE_MINIMAP_PERSISTENT_GUTTER = 48; function singleToolCallLabel(entry: WorkLogEntry): string {  const toolPresentation = resolveWorkEntryToolPresentation(entry, "completed");  if (toolPresentation) return toolPresentation.displayName;  const command = entry.command?.trim();  if (command) return command;  const heading = normalizeCompactToolLabel(entry.toolTitle || entry.label);  return `${heading.charAt(0).toUpperCase()}${heading.slice(1)}`;} export function workEntryDisplayLabel(entry: WorkLogEntry, workspaceRoot: string | undefined) {  const toolPresentation = resolveWorkEntryToolPresentation(entry);  if (toolPresentation) return toolPresentation.displayName;  if (entry.command) return entry.command;  if (entry.detail) return entry.detail;  const [firstPath] = entry.changedFiles ?? [];  if (firstPath) {    const path = formatWorkspaceRelativePath(firstPath, workspaceRoot);    return entry.changedFiles!.length === 1      ? path      : `${path} +${entry.changedFiles!.length - 1} more`;  }  const heading = normalizeCompactToolLabel(entry.toolTitle || entry.label);  return `${heading.charAt(0).toUpperCase()}${heading.slice(1)}`;} export function liveWorkEntryLabel(  entry: WorkLogEntry,  workspaceRoot: string | undefined,  active: boolean,) {  const status = liveActivityToolStatus(entry.toolLifecycleStatus, active);  const toolPresentation = resolveWorkEntryToolPresentation({    ...entry,    toolLifecycleStatus: status,  });  if (toolPresentation) return toolPresentation.displayName;  const command = entry.command?.trim();  if (command) {    const verb =      status === "inProgress"        ? "Running"        : status === "failed"          ? "Failed"          : status === "declined"            ? "Declined"            : status === "stopped"              ? "Stopped"              : "Ran";    return `${verb} ${commandProgramName(command) ?? "command"}`;  }  return workEntryDisplayLabel(entry, workspaceRoot);} export function workEntryIsVisibleInGroup(  entry: WorkLogEntry,  expandedToolGroupEntry = false,): boolean {  return (    (expandedToolGroupEntry &&      (entry.toolLifecycleStatus === "inProgress" ||        entry.sourceActivityKind === "task.progress")) ||    !workEntryIndicatesToolNeutralStatus(entry)  );} export interface WorkGroupScrollAnchor {  readonly entryId: string;  readonly offset: number;} /** Restore a visible tool, including a position partway through its expanded output. */export function resolveWorkGroupScrollIndex(  entries: ReadonlyArray<{ readonly id: string }>,  anchor: WorkGroupScrollAnchor | undefined,): { index: number; viewOffset: number } | undefined {  if (!anchor) return undefined;  const index = entries.findIndex((entry) => entry.id === anchor.entryId);  return index < 0 ? undefined : { index, viewOffset: -anchor.offset };} /** Only newly appended calls may follow the end, never status or output updates. */export function shouldFollowWorkGroupAppend(  previous: ReadonlyArray<{ readonly id: string }>,  entries: ReadonlyArray<{ readonly id: string }>,  distanceFromEnd: number,): boolean {  return (    previous.length > 0 &&    entries.length > previous.length &&    distanceFromEnd <= 1 &&    previous.every((entry, index) => entry.id === entries[index]?.id)  );} export interface TimelineEndState {  readonly isAtEnd?: boolean;  readonly contentLength?: number;  readonly scroll?: number;  readonly scrollLength?: number;} /** * Follow re-arm band above the hard bottom. Strict on purpose: LegendList's * isNearEnd fires within half a viewport, which re-armed live-follow while the * user was reading history and yanked them back down on the next stream chunk. * A small pixel band (instead of the 1px isAtEnd epsilon alone) keeps re-arming * reliable while streaming content is still growing under the viewport. */const TIMELINE_FOLLOW_REARM_THRESHOLD_PX = 40; export function resolveTimelineIsAtEnd(state: TimelineEndState | undefined): boolean | undefined {  if (!state) {    return undefined;  }  const { contentLength, scroll, scrollLength } = state;  if (contentLength === undefined || scroll === undefined || scrollLength === undefined) {    return state.isAtEnd;  }  // contentLength includes the composer inset spacer, but the composer hides  // the same amount of viewport, so the inset cancels: plain  // contentLength - scroll - scrollLength is the gap between the last real row  // and the visible edge above the composer. LegendList's own isAtEnd subtracts  // the inset and is true anywhere in the bottom composer-height band, so it is  // only a fallback here, never a short-circuit.  return contentLength - scroll - scrollLength <= TIMELINE_FOLLOW_REARM_THRESHOLD_PX;} export function shouldPreserveAssistantLineBreaks(text: string): boolean {  return /^★ Insight(?:\s|─)/mu.test(text);} export function resolveTimelineMinimapHeightStyle(itemCount: number): string {  const naturalHeight = Math.max(1, (itemCount - 1) * TIMELINE_MINIMAP_ITEM_SPACING);  return `min(${naturalHeight}px, ${TIMELINE_MINIMAP_MAX_HEIGHT_CSS})`;} export function resolveTimelineMinimapTopPercent(index: number, itemCount: number): number {  if (itemCount <= 1) {    return 0;  }  return (Math.max(0, Math.min(index, itemCount - 1)) / (itemCount - 1)) * 100;} export function resolveTimelineMinimapIndexFromPointer(input: {  readonly itemCount: number;  readonly railTop: number;  readonly railHeight: number;  readonly pointerY: number;}): number | null {  if (input.itemCount <= 0 || input.railHeight <= 0) {    return null;  }  if (input.itemCount === 1) {    return 0;  }   const progress = Math.max(0, Math.min(1, (input.pointerY - input.railTop) / input.railHeight));  return Math.max(0, Math.min(input.itemCount - 1, Math.round(progress * (input.itemCount - 1))));} export function resolveTimelineMinimapCurrentIndex(input: {  readonly scrollTop: number;  readonly scrollBottom: number;  readonly itemBounds: ReadonlyArray<{    readonly top: number | null;    readonly height: number | null;  }>;}): number | null {  let precedingIndex: number | null = null;   for (const [index, item] of input.itemBounds.entries()) {    if (item.top === null) {      continue;    }    const inView =      item.top < input.scrollBottom && item.top + Math.max(1, item.height ?? 1) > input.scrollTop;    if (inView) {      // The first visible marker is the turn at the reader's current position.      return index;    }    if (item.top <= input.scrollTop) {      precedingIndex = index;    }  }   return precedingIndex;} export function resolveTimelineMinimapHasPersistentGutter(viewportWidth: number): boolean {  if (!Number.isFinite(viewportWidth) || viewportWidth <= 0) {    return false;  }   const contentWidth = Math.min(viewportWidth, TIMELINE_CONTENT_MAX_WIDTH);  const sideGutter = Math.max(0, (viewportWidth - contentWidth) / 2);  return sideGutter >= TIMELINE_MINIMAP_PERSISTENT_GUTTER;} const TIMELINE_MINIMAP_HIT_STRIP_LEFT = 12;const TIMELINE_MINIMAP_HIT_STRIP_MAX_WIDTH = 40;const TIMELINE_MINIMAP_EXPANDED_HIT_STRIP_WIDTH = "22rem"; /** * The minimap overlays the viewport's left edge while the content column is * centered, so the side gutter between them shrinks under browser zoom or a * narrow pane. A fixed-width hover strip would then sit on top of the message * text and swallow its pointer events. Cap the strip's width so it never * extends past the gutter into the content column; 0 disables the strip. */export function resolveTimelineMinimapHitStripWidth(viewportWidth: number): number {  if (!Number.isFinite(viewportWidth) || viewportWidth <= 0) {    return 0;  }   const contentWidth = Math.min(viewportWidth, TIMELINE_CONTENT_MAX_WIDTH);  const sideGutter = Math.max(0, (viewportWidth - contentWidth) / 2);  return Math.max(    0,    Math.min(      TIMELINE_MINIMAP_HIT_STRIP_MAX_WIDTH,      Math.floor(sideGutter) - TIMELINE_MINIMAP_HIT_STRIP_LEFT,    ),  );} /** * Once the preview is open, keep the full preview and the space leading to it * interactive. The collapsed strip remains gutter-capped so it cannot block * selecting message text. */export function resolveTimelineMinimapInteractiveWidth(  collapsedWidth: number,  expanded: boolean,): number | string {  return expanded ? TIMELINE_MINIMAP_EXPANDED_HIT_STRIP_WIDTH : collapsedWidth;} function computeElapsedMs(startIso: string, endIso: string): number | null {  const start = Date.parse(startIso);  const end = Date.parse(endIso);  if (!Number.isFinite(start) || !Number.isFinite(end)) return null;  return Math.max(0, end - start);} function maxIsoTimestamp(a: string | null, b: string | null): string | null {  if (a === null) return b;  if (b === null) return a;  const aMs = Date.parse(a);  const bMs = Date.parse(b);  if (!Number.isFinite(aMs)) return b;  if (!Number.isFinite(bMs)) return a;  return bMs > aMs ? b : a;} export interface TimelineDurationMessage {  id: string;  role: "user" | "assistant" | "system";  createdAt: string;  updatedAt: string;  streaming: boolean;} export type TimelineLatestTurn = Pick<  OrchestrationLatestTurn,  "turnId" | "state" | "startedAt" | "completedAt">; const LIVE_ACTIVITY_ROW_ID = "live-activity-row"; export type MessagesTimelineRow =  | {      kind: "work";      id: string;      createdAt: string;      groupedEntries: WorkLogEntry[];      isExpandedToolGroup: boolean;      displayLabel?: string;    }  | {      kind: "work-live";      id: string;      createdAt: string;      entry: WorkLogEntry;      groupedEntries: WorkLogEntry[];      groupId: string;      expanded: boolean;      active: boolean;    }  | {      kind: "work-toggle";      id: string;      createdAt: string;      turnId?: TurnId | null;      groupId: string;      hiddenCount: number;      expanded: boolean;      summary: string;      summaryKind: ToolGroupSummaryKind;      toolSurface?: WorkLogEntry["toolSurface"];      toolIcon?: WorkLogEntry["toolIcon"];      summaryToolIcon?: "browser" | "device" | "t3-code" | "pull-request";      hasFailure: boolean;    }  | {      kind: "turn-fold";      id: string;      createdAt: string;      turnId: TurnId;      label: string;      expanded: boolean;    }  | {      kind: "context-compaction";      id: string;      createdAt: string;      label: string;    }  | {      kind: "message";      id: string;      createdAt: string;      message: ChatMessage;      durationStart: string;      showAssistantMeta: boolean;      showAssistantCopyButton: boolean;      assistantCopyStreaming: boolean;      assistantTurnDiffSummary?: TurnDiffSummary | undefined;      revertTurnCount?: number | undefined;    }  | {      kind: "assistant-meta";      id: string;      createdAt: string;      message: ChatMessage;      showAssistantCopyButton: boolean;      assistantCopyStreaming: boolean;    }  | {      kind: "proposed-plan";      id: string;      createdAt: string;      proposedPlan: ProposedPlan;    }  | {      kind: "working";      id: string;      createdAt: string | null;    }  | {      kind: "thinking";      id: string;      createdAt: string | null;    }; export interface StableMessagesTimelineRowsState {  byId: Map<string, MessagesTimelineRow>;  result: MessagesTimelineRow[];} export function computeMessageDurationStart(  messages: ReadonlyArray<TimelineDurationMessage>,): Map<string, string> {  const result = new Map<string, string>();  let lastBoundary: string | null = null;   for (const message of messages) {    if (message.role === "user") {      lastBoundary = message.createdAt;    }    result.set(message.id, lastBoundary ?? message.createdAt);    if (message.role === "assistant" && !message.streaming) {      lastBoundary = message.updatedAt;    }  }   return result;} function workGroupIdentity(timelineEntryId: string, entry: WorkLogEntry): string {  return entry.toolCallId    ? `tool:${entry.turnId ?? "no-turn"}:${entry.toolCallId}`    : timelineEntryId;} function workGroupId(timelineEntryId: string, entry: WorkLogEntry): string {  return `work-group:${workGroupIdentity(timelineEntryId, entry)}`;} function expandedWorkGroupRow(  groupId: string,  createdAt: string,  groupedEntries: WorkLogEntry[],): Extract<MessagesTimelineRow, { kind: "work" }> {  return {    kind: "work",    id: `${groupId}:details`,    createdAt,    groupedEntries,    isExpandedToolGroup: true,  };} export function resolveAssistantMessageCopyState({  text,  showCopyButton,  streaming,}: {  text: string | null;  showCopyButton: boolean;  streaming: boolean;}) {  const hasText = text !== null && text.trim().length > 0;  const visible = showCopyButton && hasText && !streaming;  return {    text: hasText ? (visible ? renderCodexDirectivesForCopy(text) : text) : null,    visible,  };} function deriveTerminalAssistantMessageIds(timelineEntries: ReadonlyArray<TimelineEntry>) {  const lastAssistantMessageIdByResponseKey = new Map<string, string>();  let nullTurnResponseIndex = 0;   for (const timelineEntry of timelineEntries) {    if (timelineEntry.kind !== "message") {      continue;    }    const { message } = timelineEntry;    if (message.role === "user") {      nullTurnResponseIndex += 1;      continue;    }    if (message.role !== "assistant") {      continue;    }     const responseKey = message.turnId      ? `turn:${message.turnId}`      : `unkeyed:${nullTurnResponseIndex}`;    lastAssistantMessageIdByResponseKey.set(responseKey, message.id);  }   return new Set(lastAssistantMessageIdByResponseKey.values());} interface TurnFold {  turnId: TurnId;  anchorEntryId: string;  createdAt: string;  hiddenEntryIds: ReadonlySet<string>;  label: string;} /** * The session's running turn is authoritative when latestTurn briefly lags or * regresses behind it. Otherwise, the latest turn counts as unsettled while it * is still running (or has not recorded a completion). This is deliberately * keyed on turn lifecycle rather than transient working state: right after the * user sends a message, the previous turn is still the "active" one until the * server creates the new turn, and folding must not flicker through that window. */

Lines 1–500 of 1418.