2085 lines · 66.8 KB · TypeScript
RawDownload
import {  ApprovalRequestId,  isToolLifecycleItemType,  ProviderApprovalOption,  ProviderRequestKind,} from "@t3tools/contracts";import type {  OrchestrationLatestTurn,  OrchestrationThread,  OrchestrationThreadActivity,  ToolLifecycleItemType,  TurnId,  UserInputQuestion,} from "@t3tools/contracts";import { formatDuration } from "@t3tools/shared/orchestrationTiming";import {  commandDetailRepeatsCommand,  extractCommandOutputText,  isWorktreeSetupActivity,  liveActivityToolStatus,  normalizeCompactToolLabel,  omitSupersededLifecycleMarkers,  resolveWorkEntryToolPresentation,  summarizeToolGroup,  toolGroupAction,  toolGroupSummaryKind,  type ToolGroupSummaryKind,} from "@t3tools/client-runtime/work-log/presentation";import { extractToolActivityPresentation } from "@t3tools/client-runtime/work-log/tool-presentation";import { commandProgramName } from "@t3tools/client-runtime/work-log/command-label"; import * as Arr from "effect/Array";import * as Order from "effect/Order";import * as Schema from "effect/Schema"; export interface PendingApproval {  readonly requestId: ApprovalRequestId;  readonly requestKind: ProviderRequestKind;  readonly createdAt: string;  readonly detail?: string;  readonly appName?: string;  readonly options?: ReadonlyArray<ProviderApprovalOption>;} const isProviderRequestKind = Schema.is(ProviderRequestKind);const isProviderApprovalOption = Schema.is(ProviderApprovalOption); export interface PendingUserInput {  readonly requestId: ApprovalRequestId;  readonly createdAt: string;  readonly questions: ReadonlyArray<UserInputQuestion>;} export interface PendingUserInputDraftAnswer {  readonly selectedOptionValues?: ReadonlyArray<string>;  readonly customAnswer?: string;} export interface ThreadFeedActivity {  readonly id: string;  readonly createdAt: string;  readonly turnId: TurnId | null;  readonly summary: string;  readonly detail: string | null;  readonly canExpand: boolean;  readonly getFullDetail: () => string | null;  readonly getCopyText: () => string;  readonly icon:    | "agent"    | "alert"    | "browser"    | "check"    | "command"    | "computer"    | "edit"    | "eye"    | "globe"    | "hammer"    | "message"    | "warning"    | "wrench"    | "zap";  readonly toolLike: boolean;  readonly status: "success" | "failure" | "neutral" | null;  readonly lifecycleStatus?: WorkLogToolLifecycleStatus;  readonly workEntry: WorkLogEntry;  readonly groupedToolDetail?: boolean;  readonly live?: boolean;} type WorkLogToolLifecycleStatus = "inProgress" | "completed" | "failed" | "declined" | "stopped"; export interface WorkLogEntry {  id: string;  createdAt: string;  turnId: TurnId | null;  label: string;  detail?: string;  viewedImagePath?: string;  command?: string;  rawCommand?: string;  changedFiles?: ReadonlyArray<string>;  tone: "thinking" | "tool" | "info" | "error";  toolTitle?: string;  toolSurface?: import("@t3tools/contracts").ToolActivitySurface;  toolIcon?: import("@t3tools/contracts").ToolActivityIcon;  toolSource?: import("@t3tools/contracts").ToolActivitySource;  itemType?: ToolLifecycleItemType;  requestKind?: PendingApproval["requestKind"];  toolLifecycleStatus?: WorkLogToolLifecycleStatus;  sourceActivityKind?: OrchestrationThreadActivity["kind"];  toolCallId?: string;  agentSpawn?: boolean;  toolData?: unknown;} interface DerivedWorkLogEntry extends WorkLogEntry {  sourceActivityKind: OrchestrationThreadActivity["kind"];  collapseKey?: string;  /** Grouping key for subagent lifecycle rows (one row per agent). */  taskId?: string;} type RawThreadFeedEntry =  | {      readonly type: "message";      readonly id: string;      readonly createdAt: string;      readonly message: OrchestrationThread["messages"][number];    }  | {      readonly type: "activity";      readonly id: string;      readonly createdAt: string;      readonly turnId: TurnId | null;      readonly activity: ThreadFeedActivity;    }; export type ThreadFeedEntry =  | Extract<RawThreadFeedEntry, { type: "message" }>  | {      readonly type: "activity-group";      readonly id: string;      readonly createdAt: string;      readonly turnId: TurnId | null;      readonly activities: ReadonlyArray<ThreadFeedActivity>;    }  | {      readonly type: "work-toggle";      readonly id: string;      readonly createdAt: string;      readonly turnId: TurnId | null;      readonly groupId: string;      readonly hiddenCount: number;      readonly expanded: boolean;      readonly summary: string;      readonly summaryKind: ToolGroupSummaryKind;      readonly toolSurface?: WorkLogEntry["toolSurface"];      readonly toolIcon?: WorkLogEntry["toolIcon"];      readonly summaryToolIcon?: "browser" | "t3-code";      readonly hasFailure: boolean;      readonly live: boolean;      readonly shimmer: boolean;    }  | {      readonly type: "turn-fold";      readonly id: string;      readonly createdAt: string;      readonly turnId: TurnId;      readonly label: string;      readonly expanded: boolean;    }; export type ThreadFeedLatestTurn = Pick<  OrchestrationLatestTurn,  "turnId" | "state" | "startedAt" | "completedAt">; type ThreadFeedActivityGroup = Extract<ThreadFeedEntry, { readonly type: "activity-group" }>; // These keys are immutable inputs. Weak caches release old histories with their source data.const activityEntriesCache = new WeakMap<  ReadonlyArray<OrchestrationThreadActivity>,  ReadonlyArray<Extract<RawThreadFeedEntry, { readonly type: "activity" }>>>();const messageEntriesCache = new WeakMap<  OrchestrationThread["messages"][number],  Extract<RawThreadFeedEntry, { readonly type: "message" }>>();const activityGroupsCache = new WeakMap<ThreadFeedActivity, ThreadFeedActivityGroup>();const presentedActivityGroupsCache = new WeakMap<  ThreadFeedActivityGroup,  {    readonly unsettledTurnId: TurnId | null;    readonly isWorking: boolean;    readonly activeTail: boolean;    readonly rows: ReadonlyArray<ThreadFeedEntry>;  }>();const turnFoldRowsCache = new WeakMap<  ThreadFeedEntry,  Extract<ThreadFeedEntry, { readonly type: "turn-fold" }>>(); export function isContextCompactionActivityGroup(  entry: Extract<ThreadFeedEntry, { readonly type: "activity-group" }>,): boolean {  return (    entry.activities.length === 1 &&    entry.activities[0]?.workEntry.sourceActivityKind === "context-compaction"  );} function requestKindFromRequestType(requestType: unknown): PendingApproval["requestKind"] | null {  switch (requestType) {    case "command_execution_approval":    case "exec_command_approval":      return "command";    case "file_read_approval":      return "file-read";    case "file_change_approval":    case "apply_patch_approval":      return "file-change";    case "mcp_elicitation_approval":      return "mcp-elicitation";    default:      return null;  }} function isStalePendingRequestFailureDetail(detail: string | undefined): boolean {  const normalized = detail?.toLowerCase();  if (!normalized) {    return false;  }  return (    normalized.includes("stale pending approval request") ||    normalized.includes("stale pending user-input request") ||    normalized.includes("unknown pending approval request") ||    normalized.includes("unknown pending permission request") ||    normalized.includes("unknown pending user-input request")  );} function parseApprovalRequestId(value: unknown): ApprovalRequestId | null {  return typeof value === "string" && value.length > 0 ? ApprovalRequestId.make(value) : null;} function parseUserInputQuestions(  payload: Record<string, unknown> | null,): ReadonlyArray<UserInputQuestion> | null {  const questions = payload?.questions;  if (!Array.isArray(questions)) {    return null;  }   const parsed = questions    .map<UserInputQuestion | null>((entry) => {      if (!entry || typeof entry !== "object") return null;      const question = entry as Record<string, unknown>;      if (        typeof question.id !== "string" ||        typeof question.header !== "string" ||        typeof question.question !== "string" ||        !Array.isArray(question.options)      ) {        return null;      }      const options = question.options        .map<UserInputQuestion["options"][number] | null>((option) => {          if (!option || typeof option !== "object") return null;          const record = option as Record<string, unknown>;          if (typeof record.label !== "string" || typeof record.description !== "string") {            return null;          }          return {            label: record.label,            description: record.description,            ...(typeof record.value === "string" ? { value: record.value } : {}),          };        })        .filter((option): option is UserInputQuestion["options"][number] => option !== null);      if (options.length === 0 && question.allowCustomAnswer === false) {        return null;      }      return {        id: question.id,        header: question.header,        question: question.question,        options,        multiSelect: question.multiSelect === true,        ...(typeof question.allowCustomAnswer === "boolean"          ? { allowCustomAnswer: question.allowCustomAnswer }          : {}),      };    })    .filter((question): question is UserInputQuestion => question !== null);   return parsed.length > 0 ? parsed : null;} function normalizeDraftAnswer(value: string | undefined): string | null {  if (typeof value !== "string") {    return null;  }  const trimmed = value.trim();  return trimmed.length > 0 ? trimmed : null;} function resolvePendingUserInputOptionValue(  question: UserInputQuestion,  value: string,): string | null {  if (question.options.some((option) => option.value === value)) {    return value;  }   const label = value.trim();  return label.length > 0 &&    question.options.some((option) => option.value === undefined && option.label.trim() === label)    ? label    : null;} function normalizeSelectedOptionValues(  question: UserInputQuestion,  value: ReadonlyArray<string> | undefined,): ReadonlyArray<string> {  if (!Array.isArray(value)) {    return [];  }   return Array.from(    new Set(      value        .map((entry) => resolvePendingUserInputOptionValue(question, entry))        .filter((entry): entry is string => entry !== null),    ),  );} function resolvePendingUserInputAnswer(  question: UserInputQuestion,  draft: PendingUserInputDraftAnswer | undefined,): string | ReadonlyArray<string> | null {  const customAnswer =    question.allowCustomAnswer === false ? null : normalizeDraftAnswer(draft?.customAnswer);  if (customAnswer) {    return customAnswer;  }   const selectedOptionValues = normalizeSelectedOptionValues(question, draft?.selectedOptionValues);  if (question.multiSelect) {    return selectedOptionValues.length > 0 ? selectedOptionValues : null;  }  return selectedOptionValues[0] ?? null;} /** Some providers settle agents through task.updated instead of task.completed. */const MOBILE_TERMINAL_UPDATE_STATUSES: ReadonlySet<string> = new Set([  "completed",  "failed",  "cancelled",  "interrupted",]); function isTerminalTaskUpdate(activity: OrchestrationThreadActivity): boolean {  if (activity.kind !== "task.updated") {    return false;  }  const payload =    activity.payload && typeof activity.payload === "object"      ? (activity.payload as Record<string, unknown>)      : null;  return (    typeof payload?.status === "string" &&    (MOBILE_TERMINAL_UPDATE_STATUSES.has(payload.status) ||      (payload.timelineBypass === true && payload.status === "idle"))  );} /** * Quiet-timeline guarantee (mirrors web's session-logic): agent-internal * activity lives in the Agents sheet, not the work log. Terminal rows are * kept — with no Agents surface on mobile they are the terminal signal * (a surface that hides rows must keep its own terminal signal). That means * task.completed and terminal task.updated, including Antigravity cancellation. */function isAgentInternalActivity(activity: OrchestrationThreadActivity): boolean {  const payload =    activity.payload && typeof activity.payload === "object"      ? (activity.payload as Record<string, unknown>)      : null;  if (!payload) {    return false;  }  const isTerminalTaskRow = activity.kind === "task.completed" || isTerminalTaskUpdate(activity);  if (payload.timelineBypass === true && !isTerminalTaskRow) {    return true;  }  // agentId marks ownership, not "hide me": a NESTED AGENT's terminal row is  // the only signal mobile gets (no Agents sheet), so it stays. Only an  // agent's own background work (stamped "background") is internal — same  // rule as web (review finding: hiding on agentId alone dropped nested  // completions with no replacement UI).  const ownedByAgent = typeof payload.agentId === "string" && payload.agentId.trim().length > 0;  if (!ownedByAgent) {    return false;  }  return !(isTerminalTaskRow && payload.agentKind === "agent");} function deriveWorkLogEntries(  activities: ReadonlyArray<OrchestrationThreadActivity>,): DerivedWorkLogEntry[] {  const ordered = Arr.sort(activities, activityOrder);  const entries: DerivedWorkLogEntry[] = [];  for (const activity of ordered) {    if (activity.tone !== "error" && isWorktreeSetupActivity(activity.kind)) continue;    if (activity.kind === "tool.started") continue;    if (activity.kind === "task.started") continue;    if (activity.kind === "task.updated" && !isTerminalTaskUpdate(activity)) continue;    if (activity.kind === "tool.progress") continue;    if (activity.kind === "context-window.updated") continue;    if (activity.summary === "Checkpoint captured") continue;    if (isNoContentRuntimeWarning(activity)) continue;    if (isPlanBoundaryToolActivity(activity)) continue;    if (isAgentInternalActivity(activity)) continue;    entries.push(toDerivedWorkLogEntry(activity));  }  return collapseDerivedWorkLogEntries(entries);} /** Adapters forward unknown wire-only SDK messages (background_tasks_changed, *  commands_changed, ...) as runtime warnings. The suffix comes from *  describeUnknownSdkMessage in the Claude adapter; a row with no displayable *  text carries nothing a user can act on, so it does not render. */function isNoContentRuntimeWarning(activity: OrchestrationThreadActivity): boolean {  return (    activity.kind === "runtime.warning" &&    activity.summary.endsWith("(no displayable text content)")  );} function isPlanBoundaryToolActivity(activity: OrchestrationThreadActivity): boolean {  if (activity.kind !== "tool.updated" && activity.kind !== "tool.completed") {    return false;  }   const payload =    activity.payload && typeof activity.payload === "object"      ? (activity.payload as Record<string, unknown>)      : null;  return typeof payload?.detail === "string" && payload.detail.startsWith("ExitPlanMode:");} function toDerivedWorkLogEntry(activity: OrchestrationThreadActivity): DerivedWorkLogEntry {  const payload =    activity.payload && typeof activity.payload === "object"      ? (activity.payload as Record<string, unknown>)      : null;  const commandPreview = extractToolCommand(payload);  const changedFiles = extractChangedFiles(payload);  const title = extractToolTitle(payload);  const toolPresentation = extractToolActivityPresentation(payload);  // Terminal task updates carry identity so they replace each child's progress row.  const isTaskActivity =    activity.kind === "task.progress" ||    activity.kind === "task.completed" ||    activity.kind === "task.updated";  const taskSummary =    isTaskActivity && typeof payload?.summary === "string" && payload.summary.length > 0      ? payload.summary      : null;  const taskDetailAsLabel =    isTaskActivity &&    !taskSummary &&    !title &&    typeof payload?.detail === "string" &&    payload.detail.length > 0      ? payload.detail      : null;  const taskLabel = taskSummary || taskDetailAsLabel;  const taskId =    isTaskActivity && typeof payload?.taskId === "string" && payload.taskId.length > 0      ? payload.taskId      : undefined;  const entry: DerivedWorkLogEntry = {    id: activity.id,    createdAt: activity.createdAt,    turnId: activity.turnId,    ...(taskId ? { taskId } : {}),    label: taskLabel || activity.summary,    tone:      activity.kind === "task.progress"        ? "thinking"        : activity.tone === "approval"          ? "info"          : activity.tone,    sourceActivityKind: activity.kind,

Lines 1–500 of 2085.