1609 lines · 49.4 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 * 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 selectedOptionLabels?: 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"    | "check"    | "command"    | "edit"    | "eye"    | "globe"    | "hammer"    | "message"    | "warning"    | "wrench"    | "zap";  readonly toolLike: boolean;  readonly status: "success" | "failure" | "neutral" | null;} const MAX_VISIBLE_WORK_LOG_ENTRIES = 1; type WorkLogToolLifecycleStatus = "inProgress" | "completed" | "failed" | "declined" | "stopped"; interface WorkLogEntry {  id: string;  createdAt: string;  turnId: TurnId | null;  label: string;  detail?: string;  command?: string;  rawCommand?: string;  changedFiles?: ReadonlyArray<string>;  tone: "thinking" | "tool" | "info" | "error";  toolTitle?: string;  itemType?: ToolLifecycleItemType;  requestKind?: PendingApproval["requestKind"];  toolLifecycleStatus?: WorkLogToolLifecycleStatus;  toolData?: unknown;} interface DerivedWorkLogEntry extends WorkLogEntry {  activityKind: 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: "working";      readonly id: string;      readonly createdAt: string;    }  | {      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 onlyToolActivities: 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">; 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,          };        })        .filter((option): option is UserInputQuestion["options"][number] => option !== null);      if (options.length === 0) {        return null;      }      return {        id: question.id,        header: question.header,        question: question.question,        options,        multiSelect: question.multiSelect === true,      };    })    .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 normalizeSelectedOptionLabels(  value: ReadonlyArray<string> | undefined,): ReadonlyArray<string> {  if (!Array.isArray(value)) {    return [];  }   return Array.from(    new Set(value.map((entry) => entry.trim()).filter((entry) => entry.length > 0)),  );} function resolvePendingUserInputAnswer(  question: UserInputQuestion,  draft: PendingUserInputDraftAnswer | undefined,): string | ReadonlyArray<string> | null {  const customAnswer = normalizeDraftAnswer(draft?.customAnswer);  if (customAnswer) {    return customAnswer;  }   const selectedOptionLabels = normalizeSelectedOptionLabels(draft?.selectedOptionLabels);  if (question.multiSelect) {    return selectedOptionLabels.length > 0 ? selectedOptionLabels : null;  }  return selectedOptionLabels[0] ?? null;} /** Codex children settle via task.updated (idle/failed/interrupted), never * task.completed — these rows are mobile's only terminal signal for them. */const MOBILE_TERMINAL_UPDATE_STATUSES: ReadonlySet<string> = new Set([  "idle",  "completed",  "failed",  "cancelled",  "interrupted",]); function isTerminalBypassUpdate(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 (    payload?.timelineBypass === true &&    typeof payload.status === "string" &&    MOBILE_TERMINAL_UPDATE_STATUSES.has(payload.status)  );} /** * 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 (Claude) AND terminal bypassed task.updated (Codex, whose * children never emit task.completed — review finding). */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" || isTerminalBypassUpdate(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.kind === "tool.started") continue;    if (activity.kind === "task.started") continue;    // Terminal bypassed updates pass: Codex children's only terminal signal.    if (activity.kind === "task.updated" && !isTerminalBypassUpdate(activity)) continue;    if (activity.kind === "tool.progress") continue;    if (activity.kind === "context-window.updated") continue;    if (activity.summary === "Checkpoint captured") continue;    if (isPlanBoundaryToolActivity(activity)) continue;    if (isAgentInternalActivity(activity)) continue;    entries.push(toDerivedWorkLogEntry(activity));  }  return collapseDerivedWorkLogEntries(entries);} 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);  // task.updated included: terminal bypassed updates (Codex children's only  // terminal signal) must carry task identity so they collapse per child  // instead of stacking anonymous "Task idle" rows.  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 &&    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,    activityKind: activity.kind,  };  const itemType = extractWorkLogItemType(payload);  const requestKind = extractWorkLogRequestKind(payload);  if (    !taskDetailAsLabel &&    payload &&    typeof payload.detail === "string" &&    payload.detail.length > 0  ) {    const detail = stripTrailingExitCode(payload.detail).output;    if (detail) {      entry.detail = detail;    }  }  if (commandPreview.command) {    entry.command = commandPreview.command;  }  if (commandPreview.rawCommand) {    entry.rawCommand = commandPreview.rawCommand;  }  if (changedFiles.length > 0) {    entry.changedFiles = changedFiles;  }  if (title) {    entry.toolTitle = title;  }  if (itemType === "mcp_tool_call") {    const data = asRecord(payload?.data);    if (data?.item !== undefined) {      entry.toolData = data.item;    }  }  if (itemType) {    entry.itemType = itemType;  }  if (requestKind) {    entry.requestKind = requestKind;  }  let toolLifecycleStatus = extractWorkLogToolLifecycleStatus(payload);  if (!toolLifecycleStatus && activity.kind === "tool.completed") {    toolLifecycleStatus = "completed";  }  if (toolLifecycleStatus) {    entry.toolLifecycleStatus = toolLifecycleStatus;  }  const collapseKey = deriveToolLifecycleCollapseKey(entry);  if (collapseKey) {    entry.collapseKey = collapseKey;  }  return entry;} function collapseDerivedWorkLogEntries(  entries: ReadonlyArray<DerivedWorkLogEntry>,): DerivedWorkLogEntry[] {  const collapsed: DerivedWorkLogEntry[] = [];  // Subagent rows collapse by identity, not adjacency (quiet-timeline  // guarantee; mirrors web's session-logic).  const taskRowIndex = new Map<string, number>();  for (const entry of entries) {    const isTaskRow =      entry.taskId !== undefined &&      (entry.activityKind === "task.progress" ||        entry.activityKind === "task.completed" ||        entry.activityKind === "task.updated");    if (isTaskRow && entry.taskId !== undefined) {      const existingIndex = taskRowIndex.get(entry.taskId);      if (existingIndex !== undefined) {        collapsed[existingIndex] = mergeDerivedWorkLogEntries(collapsed[existingIndex]!, entry);        continue;      }      taskRowIndex.set(entry.taskId, collapsed.length);      collapsed.push(entry);      continue;    }    const previous = collapsed.at(-1);    if (previous && shouldCollapseToolLifecycleEntries(previous, entry)) {      collapsed[collapsed.length - 1] = mergeDerivedWorkLogEntries(previous, entry);      continue;    }    collapsed.push(entry);  }  return collapsed;} function shouldCollapseToolLifecycleEntries(  previous: DerivedWorkLogEntry,  next: DerivedWorkLogEntry,): boolean {  if (previous.activityKind !== "tool.updated" && previous.activityKind !== "tool.completed") {    return false;  }  if (next.activityKind !== "tool.updated" && next.activityKind !== "tool.completed") {    return false;  }  if (previous.activityKind === "tool.completed") {    return false;

Lines 1–500 of 1609.