1031 lines · 34.5 KB · TypeScript
RawDownload
import {  ANTIGRAVITY_DEFAULT_MODEL,  type AssetCreateUrlInput,  type AssetCreateUrlResult,  type ChatFileAttachment,  type EnvironmentId,  isProviderDriverKind,  ProjectId,  type MessageId,  type ModelSelection,  type ProviderInteractionMode,  ProviderDriverKind,  type ProviderInstanceId,  type ServerProvider,  type ScopedProjectRef,  type ScopedThreadRef,  type ThreadId,  type TurnId,} from "@t3tools/contracts";import { resolveAssetUrl } from "@t3tools/client-runtime/state/assets";import {  squashAtomCommandFailure,  type AtomCommandResult,} from "@t3tools/client-runtime/state/runtime";import { videoMimeType } from "@t3tools/shared/video";import {  appendCodexArtifactTemplateUsePrompt,  codexArtifactTemplateUsePrompt,  type CodexArtifactTemplate,} from "@t3tools/client-runtime/codex-artifact-templates";import {  type ChatMessage,  isImageAttachment,  type SessionPhase,  type Thread,  type ThreadShell,  type TurnDiffSummary,} from "../types";import { type ComposerImageAttachment, type DraftThreadState } from "../composerDraftStore";import * as Schema from "effect/Schema";import { appAtomRegistry } from "../rpc/atomRegistry";import { environmentThreadDetails } from "../state/threads";import {  filterTerminalContextsWithText,  stripInlineTerminalContextPlaceholders,  type TerminalContextDraft,} from "../lib/terminalContext";import type { DraftThreadEnvMode } from "../composerDraftStore";import type { ComposerSubmissionIntent } from "../composer-logic";import type { TimelineEntry } from "../session-logic";import type { DesktopPreviewOverlay } from "../previewStateStore";import type { RightPanelSurface } from "../rightPanelStore";import {  NO_PROVIDER_MODEL_SELECTION,  resolveSelectableProviderInstanceEntry,  type ProviderInstanceEntry,} from "../providerInstances"; export const LAST_INVOKED_SCRIPT_BY_PROJECT_KEY = "t3code:last-invoked-script-by-project";export const MAX_HIDDEN_MOUNTED_TERMINAL_THREADS = 10;export const MAX_HIDDEN_MOUNTED_PREVIEW_THREADS = 3;export const ENVIRONMENT_RECONNECT_WARNING_GRACE_MS = 2_000; export const LastInvokedScriptByProjectSchema = Schema.Record(ProjectId, Schema.String); export function agentControlledBrowserCloseConfirmation(  surfaces: readonly RightPanelSurface[],  desktopByTabId: Readonly<Record<string, Pick<DesktopPreviewOverlay, "controller"> | undefined>>,): string | null {  const activeBrowserCount = surfaces.filter(    (surface) =>      surface.kind === "preview" &&      surface.resourceId !== null &&      desktopByTabId[surface.resourceId]?.controller === "agent",  ).length;  if (activeBrowserCount === 0) return null;  if (activeBrowserCount === 1) {    return [      "Close browser while the agent is using it?",      "The agent is actively controlling this browser. Closing it may interrupt the current browser action.",    ].join("\n");  }  return [    `Close ${activeBrowserCount} browsers while the agent is using them?`,    "The agent is actively controlling these browsers. Closing them may interrupt the current browser actions.",  ].join("\n");} export function shouldRenderPreviewMiniPlayer(  miniPlayerTabId: string | null,  renderedRightPanelSurface: RightPanelSurface | null,): boolean {  return (    miniPlayerTabId !== null &&    !(      renderedRightPanelSurface?.kind === "preview" &&      renderedRightPanelSurface.resourceId === miniPlayerTabId    )  );} export function shouldOpenProactivePullRequest(  previousTargetKey: string | null | undefined,  targetKey: string | null,): boolean {  return previousTargetKey !== undefined && targetKey !== null && targetKey !== previousTargetKey;} export function shouldOpenProactiveTurnDiff(input: {  previousRunningTurnId: TurnId | null | undefined;  runningTurnId: TurnId | null;  settledTurnId: TurnId | null;  turnCompleted: boolean;}): boolean {  return (    input.previousRunningTurnId !== undefined &&    input.previousRunningTurnId !== null &&    input.runningTurnId === null &&    input.turnCompleted &&    input.settledTurnId === input.previousRunningTurnId  );} export function resolveProactiveTurnDiffAction(input: {  checkpoint: Pick<TurnDiffSummary, "status" | "files"> | undefined;  isGitRepo: boolean | undefined;  activeSurfaceKind: RightPanelSurface["kind"] | null;}): "defer" | "ignore" | "open" {  if (input.activeSurfaceKind === "pull-request") return "ignore";  if (input.checkpoint === undefined || input.checkpoint.status === "missing") return "defer";  if (input.isGitRepo === undefined) return "defer";  if (    !input.isGitRepo ||    input.checkpoint.status !== "ready" ||    input.checkpoint.files.length === 0  ) {    return "ignore";  }  return "open";} export function codexArtifactTemplatePromptToAppend(  currentDraft: string,  template: CodexArtifactTemplate,): string | null {  return appendCodexArtifactTemplateUsePrompt(currentDraft, template) === currentDraft    ? null    : codexArtifactTemplateUsePrompt(template);} export function shouldDockDraftHeroForSubmission(input: {  isDraftHeroState: boolean;  activeThreadKey: string | null;  submissionIntent: ComposerSubmissionIntent;}): boolean {  return (    input.submissionIntent === "foreground" &&    input.isDraftHeroState &&    input.activeThreadKey !== null  );} export function shouldReleaseTimelineAnchorForToolActivity(input: {  anchorMessageId: MessageId | null;  liveFollowEnabled: boolean;  runningTurnId: TurnId | null;  timelineEntries: ReadonlyArray<TimelineEntry>;}): boolean {  if (input.anchorMessageId === null || !input.liveFollowEnabled || input.runningTurnId === null) {    return false;  }   return input.timelineEntries.some((timelineEntry) => {    if (timelineEntry.kind !== "work" || timelineEntry.entry.turnId !== input.runningTurnId) {      return false;    }     const entry = timelineEntry.entry;    return (      entry.tone === "tool" ||      entry.itemType !== undefined ||      entry.requestKind !== undefined ||      (entry.command?.trim().length ?? 0) > 0    );  });} export function toolGroupConsumesUpwardNavigation(target: EventTarget | null): boolean {  const elementTarget = target instanceof Element ? target : null;  const group = elementTarget?.closest<HTMLElement>("[data-tool-group-scroll]");  if (!group) return false;   // A nested result or the group itself can consume an upward scroll.  for (let element = elementTarget; element; element = element.parentElement) {    if (element.scrollTop > 0) {      const overflowY = getComputedStyle(element).overflowY;      if (overflowY === "auto" || overflowY === "scroll") return true;    }    if (element === group) break;  }  return false;} export function resolveDraftHeroState(input: {  isLocalDraftThread: boolean;  hasTimelineEntries: boolean;  isWorking: boolean;  draftHeroDockRequested: boolean;  backgroundSubmissionPending: boolean;}): boolean {  if (input.backgroundSubmissionPending) {    return true;  }  return (    input.isLocalDraftThread &&    !input.hasTimelineEntries &&    !input.isWorking &&    !input.draftHeroDockRequested  );} export function resolveDraftPromotionNavigationTarget(input: {  serverThreadRef: ScopedThreadRef | null;  serverThread: Pick<Thread, "latestTurn" | "session"> | null | undefined;  backgroundSubmissionPending: boolean;}): ScopedThreadRef | null {  if (input.backgroundSubmissionPending) {    return null;  }  const sessionStatus = input.serverThread?.session?.status;  const turnStarted = input.serverThread?.latestTurn?.startedAt != null;  const startupStopped =    sessionStatus === "error" || sessionStatus === "stopped" || sessionStatus === "interrupted";  // Keep local preparation feedback mounted until the server can render the  // running turn or its startup error on the canonical thread route.  return turnStarted || startupStopped ? input.serverThreadRef : null;} export function scheduleEnvironmentReconnectWarning(showWarning: () => void): () => void {  const timeoutId = globalThis.setTimeout(showWarning, ENVIRONMENT_RECONNECT_WARNING_GRACE_MS);  return () => globalThis.clearTimeout(timeoutId);} export function hasEnvironmentReconnectWarningGraceElapsed(  activeEnvironmentId: EnvironmentId | null,  elapsedEnvironmentId: EnvironmentId | null,): boolean {  return activeEnvironmentId !== null && activeEnvironmentId === elapsedEnvironmentId;} export function startNewThreadForProject(  projectRef: ScopedProjectRef | null,  handleNewThread: (projectRef: ScopedProjectRef) => Promise<unknown>,): boolean {  if (projectRef === null) return false;  void handleNewThread(projectRef);   return true;} export function resolveThreadMetadataUpdateForNextTurn(input: {  currentModelSelection: ModelSelection;  nextModelSelection?: ModelSelection;  currentBranch: string | null;  nextBranch?: string;}): {  modelSelection?: ModelSelection;  branch?: string;  worktreePath?: null;} | null {  const nextModelSelection = input.nextModelSelection;  const modelSelectionChanged =    nextModelSelection !== undefined &&    (nextModelSelection.model !== input.currentModelSelection.model ||      nextModelSelection.instanceId !== input.currentModelSelection.instanceId ||      JSON.stringify(nextModelSelection.options ?? null) !==        JSON.stringify(input.currentModelSelection.options ?? null));  const branchChanged = input.nextBranch !== undefined && input.nextBranch !== input.currentBranch;  if (!modelSelectionChanged && !branchChanged) {    return null;  }  return {    ...(modelSelectionChanged ? { modelSelection: nextModelSelection } : {}),    ...(branchChanged ? { branch: input.nextBranch, worktreePath: null } : {}),  };} export function buildLocalDraftThread(  threadId: ThreadId,  draftThread: DraftThreadState,  fallbackModelSelection: ModelSelection,): Thread {  return {    id: threadId,    environmentId: draftThread.environmentId,    projectId: draftThread.projectId,    title: "New thread",    modelSelection: fallbackModelSelection,    runtimeMode: draftThread.runtimeMode,    interactionMode: draftThread.interactionMode,    session: null,    messages: [],    createdAt: draftThread.createdAt,    updatedAt: draftThread.createdAt,    archivedAt: null,    settledOverride: null,    settledAt: null,    deletedAt: null,    latestTurn: null,    branch: draftThread.branch,    worktreePath: draftThread.worktreePath,    checkpoints: [],    activities: [],    proposedPlans: [],  };} export function buildLoadingThreadFromShell(shell: ThreadShell): Thread {  return {    ...shell,    messages: [],    proposedPlans: [],    activities: [],    checkpoints: [],    deletedAt: null,  };} export function shouldWriteThreadErrorToCurrentServerThread(input: {  activeServerThread:    | {        environmentId: EnvironmentId;        id: ThreadId;      }    | null    | undefined;  routeThreadRef: ScopedThreadRef;  targetThreadId: ThreadId;}): boolean {  return Boolean(    input.activeServerThread &&    input.targetThreadId === input.routeThreadRef.threadId &&    input.activeServerThread.environmentId === input.routeThreadRef.environmentId &&    input.activeServerThread.id === input.targetThreadId,  );} export function buildThreadTurnInterruptInput(thread: Pick<Thread, "id" | "session">): {  threadId: ThreadId;  turnId?: TurnId;} {  const runningTurnId = thread.session?.status === "running" ? thread.session.activeTurnId : null;  return {    threadId: thread.id,    ...(runningTurnId !== null ? { turnId: runningTurnId } : {}),  };} /** Use the same enabled instance for the composer, provider status, and chat actions. */export function resolveComposerProviderSelection(input: {  entries: ReadonlyArray<ProviderInstanceEntry>;  candidateInstanceIds: ReadonlyArray<ProviderInstanceId | null | undefined>;  lockedProvider: ProviderDriverKind | null;  lockedInstanceId: ProviderInstanceId | null | undefined;}) {  const requestedInstanceId = input.candidateInstanceIds.find(    (candidate) => candidate != null && candidate !== NO_PROVIDER_MODEL_SELECTION.instanceId,  );  const requestedDriverKind =    input.lockedProvider ??    input.entries.find((entry) => entry.instanceId === requestedInstanceId)?.driverKind ??    input.entries[0]?.driverKind ??    ProviderDriverKind.make("unconfigured");  const lockedContinuationGroupKey = input.lockedProvider    ? (input.entries.find((entry) => entry.instanceId === input.lockedInstanceId)        ?.continuationGroupKey ?? null)    : null;  // Missing metadata must not move Antigravity history into another Google profile.  const requiresExactInstance =    input.lockedProvider === "antigravity" &&    input.lockedInstanceId != null &&    lockedContinuationGroupKey === null;  const compatibleEntries = input.entries.filter(    (entry) =>      (!input.lockedProvider || entry.driverKind === input.lockedProvider) &&      (!lockedContinuationGroupKey || entry.continuationGroupKey === lockedContinuationGroupKey) &&      (!requiresExactInstance || entry.instanceId === input.lockedInstanceId),  );  const selectedProviderEntry =    input.candidateInstanceIds      .map((candidate) =>        compatibleEntries.find(          (entry) => entry.instanceId === candidate && entry.enabled && entry.isAvailable,        ),      )      .find((entry) => entry !== undefined) ??    resolveSelectableProviderInstanceEntry(      compatibleEntries.filter((entry) => entry.driverKind === requestedDriverKind),      undefined,    ) ??    resolveSelectableProviderInstanceEntry(compatibleEntries, undefined);  const unavailableProviderInstanceId = selectedProviderEntry    ? undefined    : input.lockedProvider      ? (input.lockedInstanceId ?? requestedInstanceId)      : requestedInstanceId;  return {    selectedProviderEntry,    requestedDriverKind,    lockedContinuationGroupKey,    unavailableProviderInstanceId,  };} /** Keep restored drafts and every plan control on the selected instance's supported mode. */export function resolveComposerInteractionMode(input: {  planModeEnabled: boolean;  provider: Pick<ServerProvider, "showInteractionModeToggle"> | null | undefined;  interactionMode: ProviderInteractionMode;}): { enabled: boolean; interactionMode: ProviderInteractionMode } {  const enabled =    input.planModeEnabled &&    input.provider != null &&    input.provider.showInteractionModeToggle !== false;  return {    enabled,    interactionMode: enabled ? input.interactionMode : "default",  };} export function getAntigravitySendBlockReason(  provider:    | Pick<ServerProvider, "driver" | "installed" | "auth" | "models" | "status">    | null    | undefined,  model: string,): string | null {  if (provider?.driver !== "antigravity") return null;  if (!provider.installed) {    return "Install Antigravity in provider settings before sending.";  }  if (provider.auth.status === "unauthenticated") {    return "Sign in to Antigravity in provider settings before sending.";  }  const slug = model.trim();  if (slug.length === 0) return "Choose an Antigravity model before sending.";  // A restart clears the account status and catalog. Session startup checks  // saved credentials and validates the model before sending the prompt.  if (provider.auth.status === "unknown") return null;  if (provider.models.length === 0) {    return "Refresh Antigravity models in provider settings before sending.";  }  // A saved model that left the catalog is kept in the picker as unavailable  // so the user sees what the thread used. The server rejects it at turn  // start, so block here unless the provider is in an error state, where a  // retry with the same model is the right move.  if (    provider.status === "ready" &&    slug !== ANTIGRAVITY_DEFAULT_MODEL &&    !provider.models.some((entry) => entry.slug === slug || entry.aliases?.includes(slug))  ) {    return "That Antigravity model is no longer available. Choose another model.";  }  return null;} export function buildRevertTurnCountByUserMessageId(input: {  supportsConversationRollback: boolean;  timelineEntries: ReadonlyArray<TimelineEntry>;  turnDiffSummaryByAssistantMessageId: ReadonlyMap<MessageId, TurnDiffSummary>;  inferredCheckpointTurnCountByTurnId: Readonly<Record<string, number | undefined>>;}) {  const byUserMessageId = new Map<MessageId, number>();  if (!input.supportsConversationRollback) {    return byUserMessageId;  }  for (let index = 0; index < input.timelineEntries.length; index += 1) {    const entry = input.timelineEntries[index];    if (!entry || entry.kind !== "message" || entry.message.role !== "user") {      continue;    }     for (let nextIndex = index + 1; nextIndex < input.timelineEntries.length; nextIndex += 1) {      const nextEntry = input.timelineEntries[nextIndex];      if (!nextEntry || nextEntry.kind !== "message") {        continue;      }      if (nextEntry.message.role === "user") {        break;      }      const summary = input.turnDiffSummaryByAssistantMessageId.get(nextEntry.message.id);      if (!summary) {        continue;      }      const turnCount =        summary.checkpointTurnCount ?? input.inferredCheckpointTurnCountByTurnId[summary.turnId];      if (typeof turnCount !== "number") {        break;      }      byUserMessageId.set(entry.message.id, Math.max(0, turnCount - 1));

Lines 1–500 of 1031.