import type { ChatMessage } from '../types/chat' import type { ProgressEntry, Session } from '../types/kanban' import { getContextUsageMetrics } from './contextUsage' import { isSessionWorking } from './sessionRuntime' import { stableMessageTimelineKey } from './messageTimelineIdentity' const CONTEXT_TOKENS_RE = /(\d[\d,]*)\s*\/\s*(\d[\d,]*)\s+tokens/i const USED_PCT_RE = /(\d{1,3})%\s*used/i const REMAINING_PCT_RE = /(\d{1,3})%\s*remaining/i export function isMessageVisibleAtDetailLevel( message: ChatMessage, detailLevel: 'summary' | 'full', ): boolean { if (detailLevel === 'full') return true return String(message.metadata?.detail_visibility ?? 'summary').trim() !== 'full' } function compactWhitespace(value: string): string { return value.replace(/\s+/g, ' ').trim() } function stripNarrativeTitlePrefix(content: string): string { const trimmed = String(content || '').trim() const markdownTitle = trimmed.match(/^\*\*(.{8,160}?)\*\*:\s+([\s\S]+)$/) if (markdownTitle) { const body = markdownTitle[2].trim() if (body.length >= 80) return body } const colonIndex = trimmed.indexOf(': ') if (colonIndex < 8 || colonIndex > 160) return trimmed const prefix = trimmed.slice(0, colonIndex).replace(/\*/g, '').trim() const body = trimmed.slice(colonIndex + 2).trim() if (body.length < 80) return trimmed if (!/[A-Za-z\u4e00-\u9fff]/.test(prefix)) return trimmed if (/^(https?|file)$/i.test(prefix)) return trimmed return body } function resultSurfacePriority(message: ChatMessage): number { const meta = (message.metadata ?? {}) as Record const transcriptKind = String(meta.transcript_kind ?? meta.kind ?? '').trim() switch (transcriptKind) { case 'child_task_result': return 80 case 'child_task_result_retry': return 79 case 'company_role_result': return 75 case 'company_role_result_retry': return 74 case 'child_result': return 70 case 'runtime_v2_assistant': return 60 case 'runtime_v2_company_assistant': return 20 case 'top_level_reply': return 40 default: break } if (String(meta.kind ?? '').trim() === 'worker_notification') { return 20 } return 0 } export function resultSurfaceDedupeKey(message: ChatMessage): string { if (resultSurfacePriority(message) <= 0) return '' const content = compactWhitespace(stripNarrativeTitlePrefix(message.content)).slice(0, 2000) return content ? `result:${content}` : '' } function parseProgressNumber(value: string | undefined): number | undefined { if (!value) return undefined const normalized = value.replace(/,/g, '').trim() if (!normalized) return undefined const parsed = Number(normalized) return Number.isFinite(parsed) ? Math.max(0, Math.round(parsed)) : undefined } function clampPct(value: number | undefined): number | undefined { if (typeof value !== 'number' || !Number.isFinite(value)) return undefined return Math.max(0, Math.min(Math.round(value), 100)) } export function deriveCompanyRuntimeDisplayStatus(session: Session | null | undefined): string | undefined { if (!session) return undefined const execMode = String(session.execMode ?? '').trim() const isCompanyLike = ( execMode === 'company' || execMode === 'org' || execMode === 'custom' || !!session.isCompanyRuntime || !!session.roleWorkItems || !!session.executorRoleWorkItems ) if (!isCompanyLike) return undefined const summaries = Object.values(session.executorRoleWorkItems ?? session.roleWorkItems ?? {}) if (summaries.length === 0) return undefined const statuses = summaries.map(summary => summary.aggregatedStatus) if (statuses.some(status => status === 'active')) return 'running' if (statuses.some(status => status === 'waiting')) return 'pending' if (statuses.some(status => status === 'pending')) return 'pending' if (statuses.every(status => status === 'failed')) return 'failed' if (statuses.every(status => status === 'done')) return 'done' if (statuses.some(status => status === 'done')) return 'done' return undefined } function deriveContextFromProgressLog(progressLog: ProgressEntry[] | undefined): { contextTokens?: number contextWindow?: number contextRemainingPct?: number } { const derived: { contextTokens?: number contextWindow?: number contextRemainingPct?: number } = {} for (const entry of progressLog ?? []) { const text = `${entry.summary ?? ''} ${entry.detail ?? ''}`.trim() if (!text) continue const tokenMatch = CONTEXT_TOKENS_RE.exec(text) if (tokenMatch) { const usedTokens = parseProgressNumber(tokenMatch[1]) const windowTokens = parseProgressNumber(tokenMatch[2]) if (typeof usedTokens === 'number') derived.contextTokens = usedTokens if (typeof windowTokens === 'number' && windowTokens > 0) derived.contextWindow = windowTokens } const remainingMatch = REMAINING_PCT_RE.exec(text) if (remainingMatch) { derived.contextRemainingPct = clampPct(parseProgressNumber(remainingMatch[1])) continue } const usedMatch = USED_PCT_RE.exec(text) if (usedMatch) { const usedPct = clampPct(parseProgressNumber(usedMatch[1])) if (typeof usedPct === 'number') derived.contextRemainingPct = 100 - usedPct } } return derived } function withDerivedSessionRuntime(session: Session): Session { const derivedContext = deriveContextFromProgressLog(session.progressLog) const contextTokens = session.contextTokens ?? derivedContext.contextTokens const contextWindow = session.contextWindow ?? derivedContext.contextWindow const contextRemainingPct = session.contextRemainingPct ?? derivedContext.contextRemainingPct if ( contextTokens === session.contextTokens && contextWindow === session.contextWindow && contextRemainingPct === session.contextRemainingPct ) { return session } return { ...session, contextTokens, contextWindow, contextRemainingPct, } } function uniqueSessionsByTaskId(sessions: Session[]): Session[] { const seen = new Set() const unique: Session[] = [] for (const session of sessions) { if (seen.has(session.taskId)) continue seen.add(session.taskId) unique.push(session) } return unique } function hasWorkItemRoleIdentity(session: Session | null | undefined): boolean { return !!( String(session?.workItemRoleId ?? '').trim() || String(session?.workItemRoleName ?? '').trim() ) } export function getWorkItemChildSessions(activeSession: Session | null, sessions: Session[]): Session[] { if (!activeSession || activeSession.mode === 'child') return [] const parentKeys = new Set() if (activeSession.sessionId) parentKeys.add(activeSession.sessionId) parentKeys.add(activeSession.taskId) const activeOriginTaskId = String(activeSession.originTaskId ?? activeSession.taskId ?? '').trim() const executionTurnOrder = new Map() for (const entry of activeSession.workItemLog ?? []) { const taskId = entry.executionTurnId || entry.runtimeTaskId if (taskId && !executionTurnOrder.has(taskId)) { executionTurnOrder.set(taskId, executionTurnOrder.size) } } const seen = new Set() const matches = sessions.filter((session) => { if (session.taskId === activeSession.taskId) return false if (seen.has(session.taskId)) return false const linkedByParent = !!session.parentSessionId && parentKeys.has(session.parentSessionId) const linkedByWorkItem = executionTurnOrder.has(session.taskId) const linkedByOrigin = !!activeOriginTaskId && String(session.originTaskId ?? '').trim() === activeOriginTaskId if (!linkedByParent && !linkedByWorkItem && !linkedByOrigin) return false seen.add(session.taskId) return true }) return matches.sort((a, b) => { const aOrder = executionTurnOrder.get(a.taskId) const bOrder = executionTurnOrder.get(b.taskId) if (aOrder != null && bOrder != null && aOrder !== bOrder) return aOrder - bOrder if (aOrder != null && bOrder == null) return -1 if (aOrder == null && bOrder != null) return 1 return b.updatedAt - a.updatedAt }) } export function getWorkItemRoleSessions(activeSession: Session | null, sessions: Session[]): Session[] { const childSessions = getWorkItemChildSessions(activeSession, sessions) if (!activeSession || activeSession.mode === 'child') return childSessions if (!hasWorkItemRoleIdentity(activeSession)) return childSessions return uniqueSessionsByTaskId([activeSession, ...childSessions]) } export function getConversationPeerSessions(activeSession: Session | null, sessions: Session[]): Session[] { if (!activeSession || activeSession.mode === 'child') return [] const activeSessionId = String(activeSession.sessionId ?? '').trim() if (!activeSessionId) return [] const seen = new Set() return sessions.filter((session) => { if (session.taskId === activeSession.taskId) return false if (seen.has(session.taskId)) return false if (String(session.sessionId ?? '').trim() !== activeSessionId) return false seen.add(session.taskId) return true }).sort((a, b) => b.updatedAt - a.updatedAt) } function isSummaryConversationSession(session: Session | null | undefined): boolean { const execMode = String(session?.execMode ?? '').trim() return execMode === 'company' || execMode === 'org' || execMode === 'custom' } function projectedDisplayScore(session: Session): number { const workingBonus = isSessionWorking(session) ? 1_000_000_000 : 0 const draftBonus = String(session.draftAssistantText ?? '').trim() ? 10_000_000_000 : 0 const messageBonus = Math.max(0, session.messageCount ?? 0) * 10_000 const progressBonus = (session.progressLog?.length ?? 0) * 100 return draftBonus + workingBonus + messageBonus + progressBonus + session.updatedAt } function runtimeProjectionScore(session: Session): number { const contextUsage = getContextUsageMetrics(session) const workingBonus = isSessionWorking(session) ? 100_000_000_000 : 0 const draftBonus = String(session.draftAssistantText ?? '').trim() ? 10_000_000_000 : 0 const contextBonus = ( typeof contextUsage.usedPct === 'number' || typeof contextUsage.usedTokens === 'number' || typeof contextUsage.windowTokens === 'number' ) ? 10_000_000_000 : 0 const tokenBonus = ( typeof session.inputTokens === 'number' || typeof session.outputTokens === 'number' || typeof session.totalTokens === 'number' ) ? 1_000_000_000 : 0 const messageBonus = Math.max(0, session.messageCount ?? 0) * 10_000 const progressBonus = (session.progressLog?.length ?? 0) * 100 return workingBonus + draftBonus + contextBonus + tokenBonus + messageBonus + progressBonus } export interface SessionConversationProjection { timelineSessions: Session[] displaySession: Session | null runtimeSession: Session | null projectedFromChild: boolean } export function projectSessionConversation( activeSession: Session | null, relatedSessions: Session[], ): SessionConversationProjection { if (!activeSession) { return { timelineSessions: [], displaySession: null, runtimeSession: null, projectedFromChild: false, } } const normalizedActiveSession = withDerivedSessionRuntime(activeSession) if (activeSession.mode === 'child' || relatedSessions.length === 0) { return { timelineSessions: [normalizedActiveSession], displaySession: normalizedActiveSession, runtimeSession: normalizedActiveSession, projectedFromChild: false, } } const normalizedRelatedSessions = relatedSessions.map(withDerivedSessionRuntime) const visibleRelatedSessions = normalizedRelatedSessions.filter((session) => session.status !== 'cancelled') const projectedSessions = visibleRelatedSessions.length > 0 ? visibleRelatedSessions : normalizedRelatedSessions const timelineSessions = uniqueSessionsByTaskId([normalizedActiveSession, ...projectedSessions]) const summaryConversation = isSummaryConversationSession(activeSession) const projectedDisplaySession = [...timelineSessions].sort( (a, b) => projectedDisplayScore(b) - projectedDisplayScore(a), )[0] ?? normalizedActiveSession const displaySession = summaryConversation ? normalizedActiveSession : projectedDisplaySession const runtimeSession = [...timelineSessions].sort( (a, b) => runtimeProjectionScore(b) - runtimeProjectionScore(a), )[0] ?? displaySession return { timelineSessions, displaySession, runtimeSession, projectedFromChild: !summaryConversation && displaySession.taskId !== activeSession.taskId, } } export function getConversationSessionView( activeSession: Session | null, runtimeSession: Session | null, timelineSessions: Session[], ): Session | null { if (!activeSession) return null const normalizedActiveSession = withDerivedSessionRuntime(activeSession) const runtimeSource = runtimeSession ?? normalizedActiveSession const companyDisplayStatus = deriveCompanyRuntimeDisplayStatus(runtimeSource) ?? deriveCompanyRuntimeDisplayStatus(normalizedActiveSession) const mergedMessageCount = getConversationMessageCount( timelineSessions.length > 0 ? timelineSessions : [normalizedActiveSession], ) return { ...normalizedActiveSession, status: companyDisplayStatus ?? (runtimeSource.status || normalizedActiveSession.status), assigneeIds: runtimeSource.assigneeIds.length > 0 ? runtimeSource.assigneeIds : normalizedActiveSession.assigneeIds, agentStatus: runtimeSource.agentStatus ?? normalizedActiveSession.agentStatus, currentTool: runtimeSource.currentTool ?? normalizedActiveSession.currentTool, displayTool: runtimeSource.displayTool ?? runtimeSource.currentTool ?? normalizedActiveSession.displayTool ?? normalizedActiveSession.currentTool, toolElapsedMs: runtimeSource.toolElapsedMs ?? normalizedActiveSession.toolElapsedMs, lastToolSummary: runtimeSource.lastToolSummary ?? normalizedActiveSession.lastToolSummary, contextTokens: runtimeSource.contextTokens ?? normalizedActiveSession.contextTokens, contextWindow: runtimeSource.contextWindow ?? normalizedActiveSession.contextWindow, contextRemainingPct: runtimeSource.contextRemainingPct ?? normalizedActiveSession.contextRemainingPct, inputTokens: runtimeSource.inputTokens ?? normalizedActiveSession.inputTokens, outputTokens: runtimeSource.outputTokens ?? normalizedActiveSession.outputTokens, totalTokens: runtimeSource.totalTokens ?? normalizedActiveSession.totalTokens, turnCostUsd: runtimeSource.turnCostUsd ?? normalizedActiveSession.turnCostUsd, sessionCostUsd: runtimeSource.sessionCostUsd ?? normalizedActiveSession.sessionCostUsd, pendingPermissionCount: runtimeSource.pendingPermissionCount ?? normalizedActiveSession.pendingPermissionCount, drainMode: runtimeSource.drainMode ?? normalizedActiveSession.drainMode, workItemProjectionId: runtimeSource.workItemProjectionId ?? normalizedActiveSession.workItemProjectionId, workItemTurnType: runtimeSource.workItemTurnType ?? normalizedActiveSession.workItemTurnType, companyProfile: normalizedActiveSession.companyProfile ?? runtimeSource.companyProfile, workItemRoleId: normalizedActiveSession.workItemRoleId ?? runtimeSource.workItemRoleId, workItemRoleName: normalizedActiveSession.workItemRoleName ?? runtimeSource.workItemRoleName, workItemGate: normalizedActiveSession.workItemGate ?? runtimeSource.workItemGate, employeeAssignment: normalizedActiveSession.employeeAssignment ?? runtimeSource.employeeAssignment, selectedExecutionAgent: runtimeSource.selectedExecutionAgent ?? normalizedActiveSession.selectedExecutionAgent, draftAssistantText: runtimeSource.draftAssistantText ?? normalizedActiveSession.draftAssistantText, draftUpdatedAt: runtimeSource.draftUpdatedAt ?? normalizedActiveSession.draftUpdatedAt, draftIteration: runtimeSource.draftIteration ?? normalizedActiveSession.draftIteration, draftTurnId: runtimeSource.draftTurnId ?? normalizedActiveSession.draftTurnId, runtimeControlState: runtimeSource.runtimeControlState ?? normalizedActiveSession.runtimeControlState, canStop: runtimeSource.canStop ?? normalizedActiveSession.canStop, canResume: runtimeSource.canResume ?? normalizedActiveSession.canResume, resumeParentTaskId: runtimeSource.resumeParentTaskId ?? normalizedActiveSession.resumeParentTaskId, resumeParentSessionId: runtimeSource.resumeParentSessionId ?? normalizedActiveSession.resumeParentSessionId, pendingRuntimeCheckpointId: runtimeSource.pendingRuntimeCheckpointId ?? normalizedActiveSession.pendingRuntimeCheckpointId, stopIntentId: runtimeSource.stopIntentId ?? normalizedActiveSession.stopIntentId, updatedAt: Math.max( normalizedActiveSession.updatedAt, runtimeSource.updatedAt, ), messageCount: Math.max( normalizedActiveSession.messageCount ?? 0, runtimeSource.messageCount ?? 0, mergedMessageCount, ), isCompanyRuntime: !!(normalizedActiveSession.isCompanyRuntime || runtimeSource.isCompanyRuntime), workItemLog: (normalizedActiveSession.workItemLog?.length ?? 0) > 0 ? normalizedActiveSession.workItemLog : runtimeSource.workItemLog, roleWorkItems: normalizedActiveSession.roleWorkItems ?? runtimeSource.roleWorkItems, executorRoleWorkItems: normalizedActiveSession.executorRoleWorkItems ?? runtimeSource.executorRoleWorkItems, activeSubagents: normalizedActiveSession.activeSubagents ?? runtimeSource.activeSubagents, permissionRequests: normalizedActiveSession.permissionRequests ?? runtimeSource.permissionRequests, } } export function getConversationHeaderSession( activeSession: Session | null, runtimeSession: Session | null, timelineSessions: Session[], ): Session | null { if (!activeSession) return null const normalizedActiveSession = withDerivedSessionRuntime(activeSession) const runtimeSource = runtimeSession ?? normalizedActiveSession const companyDisplayStatus = deriveCompanyRuntimeDisplayStatus(runtimeSource) ?? deriveCompanyRuntimeDisplayStatus(normalizedActiveSession) const mergedMessageCount = getConversationMessageCount( timelineSessions.length > 0 ? timelineSessions : [normalizedActiveSession], ) return { ...normalizedActiveSession, status: companyDisplayStatus ?? (runtimeSource.status || normalizedActiveSession.status), agentStatus: runtimeSource.agentStatus ?? normalizedActiveSession.agentStatus, currentTool: runtimeSource.currentTool ?? normalizedActiveSession.currentTool, displayTool: runtimeSource.displayTool ?? runtimeSource.currentTool ?? normalizedActiveSession.displayTool ?? normalizedActiveSession.currentTool, toolElapsedMs: runtimeSource.toolElapsedMs ?? normalizedActiveSession.toolElapsedMs, lastToolSummary: runtimeSource.lastToolSummary ?? normalizedActiveSession.lastToolSummary, contextTokens: runtimeSource.contextTokens ?? normalizedActiveSession.contextTokens, contextWindow: runtimeSource.contextWindow ?? normalizedActiveSession.contextWindow, contextRemainingPct: runtimeSource.contextRemainingPct ?? normalizedActiveSession.contextRemainingPct, inputTokens: runtimeSource.inputTokens ?? normalizedActiveSession.inputTokens, outputTokens: runtimeSource.outputTokens ?? normalizedActiveSession.outputTokens, totalTokens: runtimeSource.totalTokens ?? normalizedActiveSession.totalTokens, turnCostUsd: runtimeSource.turnCostUsd ?? normalizedActiveSession.turnCostUsd, sessionCostUsd: runtimeSource.sessionCostUsd ?? normalizedActiveSession.sessionCostUsd, pendingPermissionCount: runtimeSource.pendingPermissionCount ?? normalizedActiveSession.pendingPermissionCount, drainMode: runtimeSource.drainMode ?? normalizedActiveSession.drainMode, selectedExecutionAgent: runtimeSource.selectedExecutionAgent ?? normalizedActiveSession.selectedExecutionAgent, runtimeControlState: runtimeSource.runtimeControlState ?? normalizedActiveSession.runtimeControlState, canStop: runtimeSource.canStop ?? normalizedActiveSession.canStop, canResume: runtimeSource.canResume ?? normalizedActiveSession.canResume, resumeParentTaskId: runtimeSource.resumeParentTaskId ?? normalizedActiveSession.resumeParentTaskId, resumeParentSessionId: runtimeSource.resumeParentSessionId ?? normalizedActiveSession.resumeParentSessionId, pendingRuntimeCheckpointId: runtimeSource.pendingRuntimeCheckpointId ?? normalizedActiveSession.pendingRuntimeCheckpointId, stopIntentId: runtimeSource.stopIntentId ?? normalizedActiveSession.stopIntentId, updatedAt: Math.max(normalizedActiveSession.updatedAt, runtimeSource.updatedAt), messageCount: Math.max( normalizedActiveSession.messageCount ?? 0, runtimeSource.messageCount ?? 0, mergedMessageCount, ), } } export function mergeConversationMessages(messageGroups: ChatMessage[][]): ChatMessage[] { const seen = new Set() const resultKeyIndex = new Map() const checkpointIndex = new Map() const merged: ChatMessage[] = [] for (const group of messageGroups) { for (const message of group) { const metadata = (message.metadata ?? {}) as Record const checkpointId = String(metadata.checkpoint_id ?? '').trim() if (checkpointId) { const existingIndex = checkpointIndex.get(checkpointId) if (existingIndex !== undefined) { const existing = merged[existingIndex] const latest = message.timestamp >= existing.timestamp ? message : existing const earliest = message.timestamp < existing.timestamp ? message : existing merged[existingIndex] = { ...existing, ...latest, id: existing.id, channelId: existing.channelId, timestamp: earliest.timestamp, metadata: { ...(existing.metadata ?? {}), ...(latest.metadata ?? {}) }, } continue } } const uiMessageId = typeof metadata.ui_message_id === 'string' ? metadata.ui_message_id.trim() : '' const resultKey = resultSurfaceDedupeKey(message) if (resultKey) { const existingIndex = resultKeyIndex.get(resultKey) if (existingIndex !== undefined) { const existing = merged[existingIndex] const candidateWins = resultSurfacePriority(message) > resultSurfacePriority(existing) const preferred = candidateWins ? message : existing const secondary = candidateWins ? existing : message merged[existingIndex] = { ...secondary, ...preferred, // A result surface keeps the chronology of the first underlying // delivery, independent of which related channel happened to be // traversed first for this render. timestamp: Math.min(existing.timestamp, message.timestamp), metadata: { ...(secondary.metadata ?? {}), ...(preferred.metadata ?? {}), ui_timeline_id: stableMessageTimelineKey(existing), }, } continue } resultKeyIndex.set(resultKey, merged.length) } const checkpointKey = checkpointId ? `checkpoint:${checkpointId}` : '' const dedupeKey = resultKey || checkpointKey || uiMessageId || `${message.sender}:${message.replyToId ?? ''}:${message.timestamp}:${message.content.trim()}` if (seen.has(dedupeKey)) continue seen.add(dedupeKey) if (checkpointId) checkpointIndex.set(checkpointId, merged.length) merged.push(message) } } return merged.sort((a, b) => ( a.timestamp === b.timestamp ? a.id.localeCompare(b.id) : a.timestamp - b.timestamp )) } /** * Build the durable transcript shown by a company/org parent session. * * A parent conversation may observe every related runtime channel so that * canonical role deliveries can be surfaced in one place. Those channels * also contain transient child turns, however, and rendering all of them in * the parent transcript makes the visible timeline change whenever the * runtime projection selects a different child. Keep the parent's committed * messages and only admit the canonical cross-channel result surfaces. */ export function selectCompanySummaryMessages( messages: ChatMessage[], parentChannelId: string, ): ChatMessage[] { const terminalAssistantTurn = (message: ChatMessage): string => { const metadata = (message.metadata ?? {}) as Record const kind = String(metadata.transcript_kind ?? metadata.kind ?? '').trim() if (kind !== 'runtime_v2_assistant' && kind !== 'runtime_v2_company_assistant') return '' return String(metadata.canonical_turn_id ?? metadata.turn_id ?? '').trim() } const parentTerminalTurns = new Set( messages .filter(message => ( message.channelId === parentChannelId && isMessageVisibleAtDetailLevel(message, 'summary') )) .map(terminalAssistantTurn) .filter(Boolean), ) const childTerminalByTurn = new Map() const durableMessages: ChatMessage[] = [] for (const message of messages) { if (message.channelId === parentChannelId) { if (isMessageVisibleAtDetailLevel(message, 'summary')) { durableMessages.push(message) } continue } const metadata = (message.metadata ?? {}) as Record const kind = String(metadata.transcript_kind ?? metadata.kind ?? '').trim() const checkpointId = String(metadata.checkpoint_id ?? '').trim() const checkpointType = String(metadata.checkpoint_type ?? '').trim() const checkpointResponseId = String(metadata.response_to_checkpoint_id ?? '').trim() const escalationResponseId = String(metadata.response_to_escalation_id ?? '').trim() if ((checkpointId && checkpointType) || checkpointResponseId || escalationResponseId) { durableMessages.push(message) continue } if ([ 'company_role_result', 'company_role_result_retry', 'child_task_result', 'child_task_result_retry', 'child_result', 'top_level_reply', ].includes(kind)) { durableMessages.push(message) continue } // Snapshot builder deliberately marks the final runtime surface as // summary-visible. Preserve that durable contract instead of reusing the // result-dedupe priority table as a visibility threshold. const isSummaryTerminal = String(metadata.detail_visibility ?? '').trim() === 'summary' && (kind === 'runtime_v2_assistant' || kind === 'runtime_v2_company_assistant') if (!isSummaryTerminal) continue const turnId = terminalAssistantTurn(message) if (!turnId) { durableMessages.push(message) continue } if (parentTerminalTurns.has(turnId)) continue const existing = childTerminalByTurn.get(turnId) if (!existing) { childTerminalByTurn.set(turnId, message) continue } const existingPriority = resultSurfacePriority(existing) const candidatePriority = resultSurfacePriority(message) if (candidatePriority > existingPriority) { childTerminalByTurn.set(turnId, message) } else if ( candidatePriority === existingPriority && (message.timestamp > existing.timestamp || (message.timestamp === existing.timestamp && message.id.localeCompare(existing.id) > 0)) ) { childTerminalByTurn.set(turnId, message) } } durableMessages.push(...childTerminalByTurn.values()) return mergeConversationMessages([durableMessages]) } export function mergeConversationProgressLog(timelineSessions: Session[]): ProgressEntry[] { const seen = new Set() const merged: ProgressEntry[] = [] for (const session of timelineSessions) { for (const entry of session.progressLog ?? []) { const dedupeKey = `${entry.timestamp}:${entry.type}:${entry.summary}:${entry.detail ?? ''}` if (seen.has(dedupeKey)) continue seen.add(dedupeKey) merged.push(entry) } } return merged.sort((a, b) => a.timestamp - b.timestamp) } export function getConversationMessageCount(timelineSessions: Session[]): number { return timelineSessions.reduce( (total, session) => total + Math.max(0, session.messageCount ?? 0), 0, ) }