import type { ThreadPendingApproval, ThreadPendingUserInput, ThreadUserInputQuestion,} from "@t3tools/client-runtime/state/thread-requests";import { resolveT3McpToolPresentation, type T3McpToolLogo, type T3McpToolPresentation,} from "@t3tools/shared/t3McpToolPresentation";import type { ChatAttachment, MessageId, OrchestrationV2Actor, OrchestrationV2CreationSource, OrchestrationV2ProjectedTurnItem, OrchestrationV2RunStatus, OrchestrationV2TurnItem, OrchestrationV2UserMessageInputIntent, RunId, ThreadId,} from "@t3tools/contracts";import { formatDuration } from "@t3tools/shared/orchestrationTiming";import * as DateTime from "effect/DateTime"; export type PendingApproval = ThreadPendingApproval;export type PendingUserInput = ThreadPendingUserInput; const MAX_VISIBLE_WORK_LOG_ENTRIES = 1; export interface PendingUserInputDraftAnswer { readonly selectedOptionLabel?: string; readonly customAnswer?: string;} export interface ThreadFeedActivity { readonly id: string; readonly createdAt: string; readonly runId: RunId | 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 logo: T3McpToolLogo | null; readonly toolLike: boolean; readonly prominent: boolean; readonly status: "success" | "failure" | "neutral" | null; readonly projectedItem: OrchestrationV2ProjectedTurnItem;} export interface ThreadFeedMessage { readonly id: MessageId; readonly role: "user" | "assistant"; readonly text: string; readonly attachments: ReadonlyArray<ChatAttachment>; readonly runId: RunId | null; readonly streaming: boolean; readonly inputIntent?: OrchestrationV2UserMessageInputIntent; readonly createdBy?: OrchestrationV2Actor; readonly creationSource?: OrchestrationV2CreationSource; readonly visibility: OrchestrationV2ProjectedTurnItem["visibility"]; readonly sourceThreadId: ThreadId; readonly createdAt: string; readonly updatedAt: string; readonly projectedItem: OrchestrationV2ProjectedTurnItem;} type RawThreadFeedEntry = | { readonly type: "message"; readonly id: string; readonly createdAt: string; readonly message: ThreadFeedMessage; } | { readonly type: "activity"; readonly id: string; readonly createdAt: string; readonly runId: RunId | 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 runId: RunId | null; readonly activities: ReadonlyArray<ThreadFeedActivity>; } | { readonly type: "work-toggle"; readonly id: string; readonly createdAt: string; readonly runId: RunId | null; readonly groupId: string; readonly hiddenCount: number; readonly expanded: boolean; readonly onlyToolActivities: boolean; } | { readonly type: "run-fold"; readonly id: string; readonly createdAt: string; readonly runId: RunId; readonly label: string; readonly expanded: boolean; }; export interface ThreadFeedLatestRun { readonly runId: RunId; readonly status: OrchestrationV2RunStatus; readonly startedAt: string | null; readonly completedAt: string | 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 resolvePendingUserInputAnswer( draft: PendingUserInputDraftAnswer | undefined,): string | null { return ( normalizeDraftAnswer(draft?.customAnswer) ?? normalizeDraftAnswer(draft?.selectedOptionLabel) );} function capitalizePhrase(value: string): string { const trimmed = value.trim(); return trimmed.length === 0 ? value : `${trimmed.charAt(0).toUpperCase()}${trimmed.slice(1)}`;} function memoizeValue<T>(build: () => T): () => T { let value: T; let initialized = false; return () => { if (!initialized) { value = build(); initialized = true; } return value; };} function itemIsToolLike(item: OrchestrationV2TurnItem): boolean { return ( item.type === "reasoning" || item.type === "command_execution" || item.type === "file_change" || item.type === "file_search" || item.type === "web_search" || item.type === "approval_request" || item.type === "user_input_request" || item.type === "dynamic_tool" || item.type === "subagent" || item.type === "error" );} function itemIsProminent(item: OrchestrationV2TurnItem): boolean { return item.type === "fork" || item.type === "thread_created" || item.type === "subagent";} function itemStatus(item: OrchestrationV2TurnItem): ThreadFeedActivity["status"] { if (!itemIsToolLike(item)) return null; if (item.type === "error" || item.status === "failed") return "failure"; return item.status === "completed" ? "success" : "neutral";} function itemIcon(item: OrchestrationV2TurnItem): ThreadFeedActivity["icon"] { switch (item.type) { case "reasoning": return "agent"; case "command_execution": return "command"; case "file_change": return "edit"; case "file_search": return "eye"; case "web_search": return "globe"; case "approval_request": case "user_input_request": case "user_message": case "assistant_message": return "message"; case "dynamic_tool": return "wrench"; case "subagent": return "hammer"; case "run_interrupt_request": case "run_interrupt_result": return "warning"; case "error": return "alert"; case "checkpoint": case "proposed_plan": case "todo_list": return "check"; case "compaction": case "handoff": case "fork": case "thread_created": return "zap"; }} function itemToolPresentation(item: OrchestrationV2TurnItem): T3McpToolPresentation | null { if (item.type !== "dynamic_tool") { return null; } return resolveT3McpToolPresentation(item.toolName) ?? resolveT3McpToolPresentation(item.title);} function itemSummary( item: OrchestrationV2TurnItem, toolPresentation: T3McpToolPresentation | null = null,): string { const title = item.title?.trim(); if (title) return toolPresentation?.displayName ?? capitalizePhrase(title); switch (item.type) { case "reasoning": return "Thinking"; case "command_execution": return "Command"; case "file_change": return `Changed ${item.fileName}`; case "file_search": return "Searched files"; case "web_search": return "Searched the web"; case "approval_request": return "Approval requested"; case "user_input_request": return "Input requested"; case "checkpoint": return "Checkpoint captured"; case "run_interrupt_request": return "Interrupt requested"; case "run_interrupt_result": return "Run interrupted"; case "error": return "Provider error"; case "compaction": return "Context compacted"; case "handoff": return "Context handed off"; case "fork": return "Thread forked"; case "thread_created": return "Thread created"; case "subagent": return "Subagent"; case "dynamic_tool": return toolPresentation?.displayName ?? item.toolName ?? "Tool call"; case "proposed_plan": return "Proposed plan"; case "todo_list": return "Plan updated"; case "user_message": return "User message"; case "assistant_message": return "Assistant message"; }} function itemPreview(item: OrchestrationV2TurnItem): string | null { switch (item.type) { case "reasoning": return item.text || null; case "command_execution": return item.input || null; case "file_change": return item.fileName; case "file_search": return item.pattern ?? null; case "web_search": return item.patterns?.join(", ") ?? null; case "approval_request": return item.prompt ?? null; case "user_input_request": return item.questions.map((question) => question.question).join(" · ") || null; case "checkpoint": return item.files.length === 1 ? (item.files[0]?.path ?? null) : `${item.files.length} changed files`; case "run_interrupt_request": case "run_interrupt_result": return item.message || null; case "error": return item.failure.message; case "compaction": case "handoff": return item.summary ?? null; case "fork": case "thread_created": return item.targetThreadId; case "subagent": return item.result ?? item.progress ?? item.prompt; case "dynamic_tool": return null; case "proposed_plan": return item.markdown || null; case "todo_list": return `${item.steps.filter((step) => step.status === "completed").length}/${item.steps.length} completed`; case "user_message": case "assistant_message": return item.text || null; }} function toFeedActivity(row: OrchestrationV2ProjectedTurnItem): ThreadFeedActivity { const item = row.item; const toolPresentation = itemToolPresentation(item); const summary = itemSummary(item, toolPresentation); const detail = itemPreview(item); const getFullDetail = memoizeValue(() => JSON.stringify( { visibility: row.visibility, sourceThreadId: row.sourceThreadId, sourceItemId: row.sourceItemId, item, }, null, 2, ), ); const getCopyText = memoizeValue(() => [summary, detail, getFullDetail()] .filter( (value, index, values): value is string => Boolean(value) && values.indexOf(value) === index, ) .join("\n"), ); return { id: `${row.visibility}:${row.sourceThreadId}:${row.sourceItemId}`, createdAt: DateTime.formatIso(item.startedAt ?? item.updatedAt), runId: item.runId, summary, detail, canExpand: true, getFullDetail, getCopyText, icon: itemIcon(item), logo: toolPresentation?.logo ?? null, toolLike: itemIsToolLike(item), prominent: itemIsProminent(item), status: itemStatus(item), projectedItem: row, };} function isEmptyMessage(entry: RawThreadFeedEntry): boolean { return ( entry.type === "message" && entry.message.text.trim().length === 0 && entry.message.attachments.length === 0 );} function groupAdjacentActivities(entries: ReadonlyArray<RawThreadFeedEntry>): ThreadFeedEntry[] { const grouped: ThreadFeedEntry[] = []; // Mutable backing array for the trailing group so appending an activity is // O(1) instead of re-copying the group (which made this loop quadratic on // long tool runs). The array is only mutated while it is the trailing group. let openGroupActivities: ThreadFeedActivity[] | null = null; let openGroupRunId: string | null = null; let openGroupHasProminent = false; for (const entry of entries) { if (isEmptyMessage(entry)) continue; if (entry.type !== "activity") { grouped.push(entry); openGroupActivities = null; continue; } if ( openGroupActivities !== null && openGroupRunId === entry.runId && !entry.activity.prominent && !openGroupHasProminent ) { openGroupActivities.push(entry.activity); continue; } openGroupActivities = [entry.activity]; openGroupRunId = entry.runId; openGroupHasProminent = entry.activity.prominent === true; grouped.push({ type: "activity-group", id: entry.id, createdAt: entry.createdAt, runId: entry.runId, activities: openGroupActivities, }); } return grouped;} function computeElapsedMs(startIso: string, endIso: string): number | null { const start = Date.parse(startIso); const end = Date.parse(endIso); return Number.isFinite(start) && Number.isFinite(end) ? Math.max(0, end - start) : null;} function maxIsoTimestamp(left: string | null, right: string | null): string | null { if (left === null) return right; if (right === null) return left; return Date.parse(right) > Date.parse(left) ? right : left;} function unsettledRunId(latestRun: ThreadFeedLatestRun | null): RunId | null { return threadFeedRunIsUnsettled(latestRun) ? latestRun.runId : null;} export function threadFeedRunIsUnsettled( run: ThreadFeedLatestRun | null,): run is ThreadFeedLatestRun { return ( run !== null && (run.status === "preparing" || run.status === "starting" || run.status === "running" || run.status === "waiting") );} interface ThreadFeedRunFold { readonly runId: RunId; readonly createdAt: string; readonly hiddenEntryIds: ReadonlySet<string>; readonly label: string;} function deriveThreadFeedRunFolds( feed: ReadonlyArray<ThreadFeedEntry>, latestRun: ThreadFeedLatestRun | null,): ReadonlyMap<string, ThreadFeedRunFold> { const terminalAssistantMessageIdByRun = new Map<RunId, string>(); for (const entry of feed) { if (entry.type === "message" && entry.message.role === "assistant" && entry.message.runId) { terminalAssistantMessageIdByRun.set(entry.message.runId, entry.id); } } const groupsByRunId = new Map< RunId, { entries: ThreadFeedEntry[]; startBoundary: string | null } >(); let pendingUserBoundary: string | null = null; for (const entry of feed) { if (entry.type === "message" && entry.message.role === "user") { pendingUserBoundary = entry.message.createdAt; continue; } const runId = entry.type === "message" && entry.message.role === "assistant" ? entry.message.runId : entry.type === "activity-group" ? entry.runId : null; if (!runId) continue; let group = groupsByRunId.get(runId); if (!group) { group = { entries: [], startBoundary: pendingUserBoundary }; pendingUserBoundary = null; groupsByRunId.set(runId, group); } group.entries.push(entry); } const activeRunId = unsettledRunId(latestRun); const foldsByAnchorId = new Map<string, ThreadFeedRunFold>();Lines 1–500 of 726. Load more
import type { ThreadPendingApproval, ThreadPendingUserInput, ThreadUserInputQuestion, } from "@t3tools/client-runtime/state/thread-requests"; import { resolveT3McpToolPresentation, type T3McpToolLogo, type T3McpToolPresentation, } from "@t3tools/shared/t3McpToolPresentation"; import type { ChatAttachment, MessageId, OrchestrationV2Actor, OrchestrationV2CreationSource, OrchestrationV2ProjectedTurnItem, OrchestrationV2RunStatus, OrchestrationV2TurnItem, OrchestrationV2UserMessageInputIntent, RunId, ThreadId, } from "@t3tools/contracts"; import { formatDuration } from "@t3tools/shared/orchestrationTiming"; import * as DateTime from "effect/DateTime"; export type PendingApproval = ThreadPendingApproval; export type PendingUserInput = ThreadPendingUserInput; const MAX_VISIBLE_WORK_LOG_ENTRIES = 1; export interface PendingUserInputDraftAnswer { readonly selectedOptionLabel?: string; readonly customAnswer?: string; } export interface ThreadFeedActivity { readonly id: string; readonly createdAt: string; readonly runId: RunId | 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 logo: T3McpToolLogo | null; readonly toolLike: boolean; readonly prominent: boolean; readonly status: "success" | "failure" | "neutral" | null; readonly projectedItem: OrchestrationV2ProjectedTurnItem; } export interface ThreadFeedMessage { readonly id: MessageId; readonly role: "user" | "assistant"; readonly text: string; readonly attachments: ReadonlyArray<ChatAttachment>; readonly runId: RunId | null; readonly streaming: boolean; readonly inputIntent?: OrchestrationV2UserMessageInputIntent; readonly createdBy?: OrchestrationV2Actor; readonly creationSource?: OrchestrationV2CreationSource; readonly visibility: OrchestrationV2ProjectedTurnItem["visibility"]; readonly sourceThreadId: ThreadId; readonly createdAt: string; readonly updatedAt: string; readonly projectedItem: OrchestrationV2ProjectedTurnItem; } type RawThreadFeedEntry = | { readonly type: "message"; readonly id: string; readonly createdAt: string; readonly message: ThreadFeedMessage; } | { readonly type: "activity"; readonly id: string; readonly createdAt: string; readonly runId: RunId | 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 runId: RunId | null; readonly activities: ReadonlyArray<ThreadFeedActivity>; } | { readonly type: "work-toggle"; readonly id: string; readonly createdAt: string; readonly runId: RunId | null; readonly groupId: string; readonly hiddenCount: number; readonly expanded: boolean; readonly onlyToolActivities: boolean; } | { readonly type: "run-fold"; readonly id: string; readonly createdAt: string; readonly runId: RunId; readonly label: string; readonly expanded: boolean; }; export interface ThreadFeedLatestRun { readonly runId: RunId; readonly status: OrchestrationV2RunStatus; readonly startedAt: string | null; readonly completedAt: string | 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 resolvePendingUserInputAnswer( draft: PendingUserInputDraftAnswer | undefined, ): string | null { return ( normalizeDraftAnswer(draft?.customAnswer) ?? normalizeDraftAnswer(draft?.selectedOptionLabel) ); } function capitalizePhrase(value: string): string { const trimmed = value.trim(); return trimmed.length === 0 ? value : `${trimmed.charAt(0).toUpperCase()}${trimmed.slice(1)}`; } function memoizeValue<T>(build: () => T): () => T { let value: T; let initialized = false; return () => { if (!initialized) { value = build(); initialized = true; } return value; }; } function itemIsToolLike(item: OrchestrationV2TurnItem): boolean { return ( item.type === "reasoning" || item.type === "command_execution" || item.type === "file_change" || item.type === "file_search" || item.type === "web_search" || item.type === "approval_request" || item.type === "user_input_request" || item.type === "dynamic_tool" || item.type === "subagent" || item.type === "error" ); } function itemIsProminent(item: OrchestrationV2TurnItem): boolean { return item.type === "fork" || item.type === "thread_created" || item.type === "subagent"; } function itemStatus(item: OrchestrationV2TurnItem): ThreadFeedActivity["status"] { if (!itemIsToolLike(item)) return null; if (item.type === "error" || item.status === "failed") return "failure"; return item.status === "completed" ? "success" : "neutral"; } function itemIcon(item: OrchestrationV2TurnItem): ThreadFeedActivity["icon"] { switch (item.type) { case "reasoning": return "agent"; case "command_execution": return "command"; case "file_change": return "edit"; case "file_search": return "eye"; case "web_search": return "globe"; case "approval_request": case "user_input_request": case "user_message": case "assistant_message": return "message"; case "dynamic_tool": return "wrench"; case "subagent": return "hammer"; case "run_interrupt_request": case "run_interrupt_result": return "warning"; case "error": return "alert"; case "checkpoint": case "proposed_plan": case "todo_list": return "check"; case "compaction": case "handoff": case "fork": case "thread_created": return "zap"; } } function itemToolPresentation(item: OrchestrationV2TurnItem): T3McpToolPresentation | null { if (item.type !== "dynamic_tool") { return null; } return resolveT3McpToolPresentation(item.toolName) ?? resolveT3McpToolPresentation(item.title); } function itemSummary( item: OrchestrationV2TurnItem, toolPresentation: T3McpToolPresentation | null = null, ): string { const title = item.title?.trim(); if (title) return toolPresentation?.displayName ?? capitalizePhrase(title); switch (item.type) { case "reasoning": return "Thinking"; case "command_execution": return "Command"; case "file_change": return `Changed ${item.fileName}`; case "file_search": return "Searched files"; case "web_search": return "Searched the web"; case "approval_request": return "Approval requested"; case "user_input_request": return "Input requested"; case "checkpoint": return "Checkpoint captured"; case "run_interrupt_request": return "Interrupt requested"; case "run_interrupt_result": return "Run interrupted"; case "error": return "Provider error"; case "compaction": return "Context compacted"; case "handoff": return "Context handed off"; case "fork": return "Thread forked"; case "thread_created": return "Thread created"; case "subagent": return "Subagent"; case "dynamic_tool": return toolPresentation?.displayName ?? item.toolName ?? "Tool call"; case "proposed_plan": return "Proposed plan"; case "todo_list": return "Plan updated"; case "user_message": return "User message"; case "assistant_message": return "Assistant message"; } } function itemPreview(item: OrchestrationV2TurnItem): string | null { switch (item.type) { case "reasoning": return item.text || null; case "command_execution": return item.input || null; case "file_change": return item.fileName; case "file_search": return item.pattern ?? null; case "web_search": return item.patterns?.join(", ") ?? null; case "approval_request": return item.prompt ?? null; case "user_input_request": return item.questions.map((question) => question.question).join(" · ") || null; case "checkpoint": return item.files.length === 1 ? (item.files[0]?.path ?? null) : `${item.files.length} changed files`; case "run_interrupt_request": case "run_interrupt_result": return item.message || null; case "error": return item.failure.message; case "compaction": case "handoff": return item.summary ?? null; case "fork": case "thread_created": return item.targetThreadId; case "subagent": return item.result ?? item.progress ?? item.prompt; case "dynamic_tool": return null; case "proposed_plan": return item.markdown || null; case "todo_list": return `${item.steps.filter((step) => step.status === "completed").length}/${item.steps.length} completed`; case "user_message": case "assistant_message": return item.text || null; } } function toFeedActivity(row: OrchestrationV2ProjectedTurnItem): ThreadFeedActivity { const item = row.item; const toolPresentation = itemToolPresentation(item); const summary = itemSummary(item, toolPresentation); const detail = itemPreview(item); const getFullDetail = memoizeValue(() => JSON.stringify( { visibility: row.visibility, sourceThreadId: row.sourceThreadId, sourceItemId: row.sourceItemId, item, }, null, 2, ), ); const getCopyText = memoizeValue(() => [summary, detail, getFullDetail()] .filter( (value, index, values): value is string => Boolean(value) && values.indexOf(value) === index, ) .join("\n"), ); return { id: `${row.visibility}:${row.sourceThreadId}:${row.sourceItemId}`, createdAt: DateTime.formatIso(item.startedAt ?? item.updatedAt), runId: item.runId, summary, detail, canExpand: true, getFullDetail, getCopyText, icon: itemIcon(item), logo: toolPresentation?.logo ?? null, toolLike: itemIsToolLike(item), prominent: itemIsProminent(item), status: itemStatus(item), projectedItem: row, }; } function isEmptyMessage(entry: RawThreadFeedEntry): boolean { return ( entry.type === "message" && entry.message.text.trim().length === 0 && entry.message.attachments.length === 0 ); } function groupAdjacentActivities(entries: ReadonlyArray<RawThreadFeedEntry>): ThreadFeedEntry[] { const grouped: ThreadFeedEntry[] = []; // Mutable backing array for the trailing group so appending an activity is // O(1) instead of re-copying the group (which made this loop quadratic on // long tool runs). The array is only mutated while it is the trailing group. let openGroupActivities: ThreadFeedActivity[] | null = null; let openGroupRunId: string | null = null; let openGroupHasProminent = false; for (const entry of entries) { if (isEmptyMessage(entry)) continue; if (entry.type !== "activity") { grouped.push(entry); openGroupActivities = null; continue; } if ( openGroupActivities !== null && openGroupRunId === entry.runId && !entry.activity.prominent && !openGroupHasProminent ) { openGroupActivities.push(entry.activity); continue; } openGroupActivities = [entry.activity]; openGroupRunId = entry.runId; openGroupHasProminent = entry.activity.prominent === true; grouped.push({ type: "activity-group", id: entry.id, createdAt: entry.createdAt, runId: entry.runId, activities: openGroupActivities, }); } return grouped; } function computeElapsedMs(startIso: string, endIso: string): number | null { const start = Date.parse(startIso); const end = Date.parse(endIso); return Number.isFinite(start) && Number.isFinite(end) ? Math.max(0, end - start) : null; } function maxIsoTimestamp(left: string | null, right: string | null): string | null { if (left === null) return right; if (right === null) return left; return Date.parse(right) > Date.parse(left) ? right : left; } function unsettledRunId(latestRun: ThreadFeedLatestRun | null): RunId | null { return threadFeedRunIsUnsettled(latestRun) ? latestRun.runId : null; } export function threadFeedRunIsUnsettled( run: ThreadFeedLatestRun | null, ): run is ThreadFeedLatestRun { return ( run !== null && (run.status === "preparing" || run.status === "starting" || run.status === "running" || run.status === "waiting") ); } interface ThreadFeedRunFold { readonly runId: RunId; readonly createdAt: string; readonly hiddenEntryIds: ReadonlySet<string>; readonly label: string; } function deriveThreadFeedRunFolds( feed: ReadonlyArray<ThreadFeedEntry>, latestRun: ThreadFeedLatestRun | null, ): ReadonlyMap<string, ThreadFeedRunFold> { const terminalAssistantMessageIdByRun = new Map<RunId, string>(); for (const entry of feed) { if (entry.type === "message" && entry.message.role === "assistant" && entry.message.runId) { terminalAssistantMessageIdByRun.set(entry.message.runId, entry.id); } } const groupsByRunId = new Map< RunId, { entries: ThreadFeedEntry[]; startBoundary: string | null } >(); let pendingUserBoundary: string | null = null; for (const entry of feed) { if (entry.type === "message" && entry.message.role === "user") { pendingUserBoundary = entry.message.createdAt; continue; } const runId = entry.type === "message" && entry.message.role === "assistant" ? entry.message.runId : entry.type === "activity-group" ? entry.runId : null; if (!runId) continue; let group = groupsByRunId.get(runId); if (!group) { group = { entries: [], startBoundary: pendingUserBoundary }; pendingUserBoundary = null; groupsByRunId.set(runId, group); } group.entries.push(entry); } const activeRunId = unsettledRunId(latestRun); const foldsByAnchorId = new Map<string, ThreadFeedRunFold>();