fix(ui): stabilize company chat result topology

This commit is contained in:
LZH-YS1998
2026-07-14 20:31:10 +08:00
parent 0a6246aff9
commit 5938b4c215
25 changed files with 1917 additions and 381 deletions
@@ -1,4 +1,103 @@
import type { ChatMessage } from '../types/chat'
import { resolveCanonicalTurnId, terminalAssistantTurnId } from './turnIdentity'
const COMMITTED_RESULT_SURFACE_KINDS = new Set([
'child_task_result',
'child_task_result_retry',
'company_role_result',
'company_role_result_retry',
'child_result',
'top_level_reply',
])
const RUNTIME_RESULT_SURFACE_KINDS = new Set([
'runtime_v2_assistant',
'runtime_v2_company_assistant',
])
function metadataValue(metadata: Record<string, unknown>, ...keys: string[]): string {
for (const key of keys) {
const value = String(metadata[key] ?? '').trim()
if (value) return value
}
return ''
}
function resultGenerationSuffix(metadata: Record<string, unknown>, kind: string): string {
const explicitAttempt = metadataValue(
metadata,
'result_attempt',
'attempt',
'attempt_index',
'retry_count',
'retryCount',
)
const attempt = explicitAttempt || (kind.endsWith('_retry') ? 'retry' : '')
const revision = metadataValue(
metadata,
'result_revision',
'delivery_revision',
'revision',
)
return [
attempt ? `attempt:${attempt}` : '',
revision ? `revision:${revision}` : '',
].filter(Boolean).join(':')
}
function withResultGeneration(
base: string,
metadata: Record<string, unknown>,
kind: string,
): string {
const suffix = resultGenerationSuffix(metadata, kind)
return suffix ? `${base}:${suffix}` : base
}
/** Stable protocol identity shared by mirrors of one committed result. */
export function stableResultDeliveryKey(message: ChatMessage): string {
const metadata = (message.metadata ?? {}) as Record<string, unknown>
const kind = String(metadata.transcript_kind ?? metadata.kind ?? '').trim()
if (!COMMITTED_RESULT_SURFACE_KINDS.has(kind) && !RUNTIME_RESULT_SURFACE_KINDS.has(kind)) {
return ''
}
const deliveryId = metadataValue(
metadata,
'canonical_delivery_id',
'result_delivery_id',
'delivery_id',
)
if (deliveryId) return `delivery:${deliveryId}`
if (RUNTIME_RESULT_SURFACE_KINDS.has(kind)) {
const turnId = resolveCanonicalTurnId(metadata)
return turnId ? `turn:${turnId}` : ''
}
// A committed result belongs to a task/work-item delivery, not merely to
// the surrounding conversation turn. Parallel roles commonly share that
// turn, so a turn-only fallback would collapse independent deliveries.
const explicitSourceTaskId = metadataValue(metadata, 'source_task_id', 'child_task_id')
const sourceTaskId = explicitSourceTaskId || (
kind === 'top_level_reply'
? ''
: metadataValue(metadata, 'task_id', 'taskId')
)
if (sourceTaskId) {
return withResultGeneration(`source-task:${sourceTaskId}`, metadata, kind)
}
const workItemId = metadataValue(metadata, 'work_item_id', 'work_item_projection_id')
if (workItemId) {
return withResultGeneration(`work-item:${workItemId}`, metadata, kind)
}
const childSessionId = metadataValue(metadata, 'child_session_id')
return childSessionId
? withResultGeneration(`child-session:${childSessionId}`, metadata, kind)
: ''
}
export function stableMessageTimelineKey(message: ChatMessage): string {
const metadata = message.metadata ?? {}
@@ -21,10 +120,19 @@ export function stableMessageTimelineKey(message: ChatMessage): string {
const retainedTimelineId = String(metadata.ui_timeline_id ?? '').trim()
if (retainedTimelineId) return retainedTimelineId
const turnId = String(metadata.canonical_turn_id ?? metadata.turn_id ?? '').trim()
if (!isUserTurn && turnId && transcriptKind === 'runtime_v2_assistant') {
// Only a streamed runtime terminal owns the live draft's React slot.
// Committed result surfaces may expose the same conversation turn through
// terminalAssistantTurnId, but their row identity must remain delivery/task
// scoped so parallel role results cannot collide.
const turnId = RUNTIME_RESULT_SURFACE_KINDS.has(transcriptKind)
? terminalAssistantTurnId(message)
: ''
if (!isUserTurn && turnId) {
return `turn:assistant:${turnId}`
}
const resultDeliveryKey = stableResultDeliveryKey(message)
if (!isUserTurn && resultDeliveryKey) return `result:${resultDeliveryKey}`
return `message:${message.id}`
}
@@ -0,0 +1,50 @@
import type { ChatMessage, ChatMessageMeta } from '../types/chat'
const TERMINAL_ASSISTANT_KINDS = new Set([
'runtime_v2_assistant',
'runtime_v2_company_assistant',
'child_task_result',
'child_task_result_retry',
'company_role_result',
'company_role_result_retry',
'child_result',
'top_level_reply',
])
export function resolveCanonicalTurnId(
metadata: ChatMessageMeta | Record<string, unknown> | null | undefined,
): string {
const source = (metadata ?? {}) as Record<string, unknown>
for (const key of [
'canonical_turn_id',
'conversation_turn_id',
'turn_id',
'execution_turn_id',
]) {
const value = String(source[key] ?? '').trim()
if (value) return value
}
return ''
}
export function terminalAssistantTurnId(message: ChatMessage): string {
if (message.sender === 'user') return ''
const metadata = (message.metadata ?? {}) as Record<string, unknown>
const kind = String(metadata.transcript_kind ?? metadata.kind ?? '').trim()
if (!TERMINAL_ASSISTANT_KINDS.has(kind)) return ''
if (kind === 'runtime_v2_company_assistant') {
// Company tool-call iterations intentionally share this transcript kind
// and canonical turn with the final answer. Treating every iteration as
// terminal hides the live draft during detail refresh, then grows it back
// on the next delta. Only the actual final surface may replace the draft.
const uiMessageId = String(metadata.ui_message_id ?? '').trim()
const isFinal = metadata.company_final_turn === true
|| !!String(metadata.result_delivery_id ?? '').trim()
|| uiMessageId.startsWith('runtime-v2-company-assistant-final:')
// Snapshot translation retains final-vs-intermediate as visibility even
// when reading records created before structured delivery ids existed.
|| String(metadata.detail_visibility ?? '').trim() === 'summary'
if (!isFinal) return ''
}
return resolveCanonicalTurnId(metadata)
}
@@ -2,8 +2,10 @@ import assert from 'node:assert/strict'
import type { ChatMessage } from '../types/chat'
import type { Session } from '../types/kanban'
import { mapBackendSession, mergeSessionDetailHasMore } from './collabSync'
import { stableMessageTimelineKey, stableResultDeliveryKey } from './messageTimelineIdentity'
import { canonicalizeSessionExecutionIdentity } from './sessionIdentity'
import { deriveCompanyRuntimeDisplayStatus, getConversationHeaderSession, getConversationSessionView, getWorkItemChildSessions, getWorkItemRoleSessions, mergeConversationMessages, projectSessionConversation, selectCompanySummaryMessages } from './workItemSessions'
import { resolveCanonicalTurnId, terminalAssistantTurnId } from './turnIdentity'
import { deriveCompanyRuntimeDisplayStatus, getConversationHeaderSession, getConversationSessionView, getWorkItemChildSessions, getWorkItemRoleSessions, mergeConversationMessages, projectSessionConversation, resultSurfaceDedupeKey, selectCompanySummaryMessages } from './workItemSessions'
function makeSession(overrides: Partial<Session> & Pick<Session, 'taskId' | 'channelId' | 'title' | 'status' | 'columnId' | 'assigneeIds' | 'priority' | 'tags' | 'progressLog' | 'createdAt' | 'updatedAt' | 'messageCount'>): Session {
return {
@@ -338,12 +340,380 @@ assert.deepEqual(
[
'parent-user',
'canonical-role-result',
'summary-company-final',
'summary-terminal-b',
'child-checkpoint',
'child-checkpoint-response',
].sort(),
)
const companyRuntimeTurn = resultMessage(
'company-runtime-turn',
'session:company-child',
'A company runtime terminal surface.',
{
source: 'engine',
transcript_kind: 'runtime_v2_company_assistant',
canonical_turn_id: 'company-turn-0009',
result_delivery_id: 'company-delivery-0009',
},
'assistant',
)
assert.equal(
stableMessageTimelineKey(companyRuntimeTurn),
'turn:assistant:company-turn-0009',
)
const conversationOnlyRuntimeTurn = resultMessage(
'company-runtime-conversation-turn',
'session:company-child',
'A terminal surface with only its conversation identity.',
{
source: 'engine',
transcript_kind: 'runtime_v2_company_assistant',
conversation_turn_id: 'conversation-turn-only',
turn_id: 'iteration-turn-must-not-win',
execution_turn_id: 'execution-turn-must-not-win',
detail_visibility: 'summary',
},
'assistant',
)
assert.equal(
resolveCanonicalTurnId(conversationOnlyRuntimeTurn.metadata),
'conversation-turn-only',
)
assert.equal(terminalAssistantTurnId(conversationOnlyRuntimeTurn), 'conversation-turn-only')
assert.equal(
terminalAssistantTurnId({
...conversationOnlyRuntimeTurn,
metadata: {
...conversationOnlyRuntimeTurn.metadata,
detail_visibility: 'full',
},
}),
'',
'a company tool-call iteration must not suppress the active draft',
)
assert.equal(
stableMessageTimelineKey(conversationOnlyRuntimeTurn),
'turn:assistant:conversation-turn-only',
)
assert.equal(
terminalAssistantTurnId({
...conversationOnlyRuntimeTurn,
metadata: {
...conversationOnlyRuntimeTurn.metadata,
transcript_kind: 'company_role_result',
},
}),
'conversation-turn-only',
'committed result kinds must participate in terminal-turn matching',
)
assert.equal(
stableMessageTimelineKey({
...conversationOnlyRuntimeTurn,
metadata: {
...conversationOnlyRuntimeTurn.metadata,
transcript_kind: 'company_role_result',
source_task_id: 'committed-source-task',
},
}),
'result:source-task:committed-source-task',
'a committed terminal may expose its turn for matching without taking the runtime draft key',
)
const project0009CtoResult = [
'Both work items have been successfully dispatched to my senior engineer. Here\'s the status:',
'',
'## Dispatch Summary',
'',
'**Work Item 1: OpenOPC Source Code Architecture Deep-Dive Analysis**',
'- ID: `1ed5f5f1-ac41-49a1-b1fa-23bbc9adab82`',
'- Owner: senior_engineer',
'- Scope: `openopc-source-analysis`',
'- Output: `/data2/bjdwhzzh/project-hku/OpenOPC_workplace/0009/openopc-architecture-analysis.md`',
'- Covers: Layered architecture, collaboration policy, seat executor pattern, and self-evolution mechanisms.',
'',
'**Work Item 2: External Multi-Agent Frameworks Architecture Research**',
'- ID: `d0307208-6b95-44c1-9b51-6bf073bbdcef`',
'- Owner: senior_engineer',
'- Scope: `external-frameworks-research`',
'- Output: `/data2/bjdwhzzh/project-hku/OpenOPC_workplace/0009/external-frameworks-analysis.md`',
'',
'Both are independent and can execute in parallel. The runtime will monitor their completion.',
].join('\n')
const project0009WorkItemSuffix = project0009CtoResult.slice(
project0009CtoResult.indexOf('OpenOPC Source Code Architecture Deep-Dive Analysis'),
)
const project0009IdSuffix = project0009CtoResult.slice(
project0009CtoResult.indexOf('`1ed5f5f1-ac41-49a1-b1fa-23bbc9adab82`'),
)
// Content fallback must not treat arbitrary Markdown colons as removable
// narrative wrappers. The old normalization repeatedly turned the full 0009
// result into these two shorter variants, which changed the rendered height.
const fallback0009Full = resultMessage(
'0009-fallback-full',
'session:company-root',
project0009CtoResult,
{ source: 'engine', transcript_kind: 'child_result' },
'assistant',
)
const fallback0009WorkItem = resultMessage(
'0009-fallback-work-item',
'session:company-child',
project0009WorkItemSuffix,
{ source: 'engine', transcript_kind: 'runtime_v2_assistant' },
'assistant',
)
const fallback0009Id = resultMessage(
'0009-fallback-id',
'session:company-child',
project0009IdSuffix,
{ source: 'engine', transcript_kind: 'runtime_v2_assistant' },
'assistant',
)
assert.notEqual(resultSurfaceDedupeKey(fallback0009Full), resultSurfaceDedupeKey(fallback0009WorkItem))
assert.notEqual(resultSurfaceDedupeKey(fallback0009WorkItem), resultSurfaceDedupeKey(fallback0009Id))
const committed0009Parent = {
...resultMessage(
'0009-parent-result',
'session:company-root',
project0009CtoResult,
{
source: 'engine',
transcript_kind: 'child_result',
source_task_id: 'cto-task-0009',
},
'assistant',
),
timestamp: 2_000,
}
const committed0009Child = {
...resultMessage(
'0009-child-result',
'session:company-child',
project0009CtoResult,
{
source: 'engine',
transcript_kind: 'child_task_result',
task_id: 'cto-task-0009',
},
'assistant',
),
timestamp: 2_010,
}
const raw0009WorkItem = {
...fallback0009WorkItem,
metadata: {
...fallback0009WorkItem.metadata,
detail_visibility: 'summary' as const,
canonical_turn_id: 'cto-turn-0009',
},
timestamp: 2_020,
}
const raw0009Id = {
...fallback0009Id,
metadata: {
...fallback0009Id.metadata,
detail_visibility: 'summary' as const,
canonical_turn_id: 'cto-turn-0009',
},
timestamp: 2_030,
}
for (const messages of [
[committed0009Parent, committed0009Child, raw0009WorkItem, raw0009Id],
[raw0009Id, raw0009WorkItem, committed0009Child, committed0009Parent],
]) {
const summary = selectCompanySummaryMessages(messages, 'session:company-root')
assert.equal(summary.length, 1)
assert.equal(summary[0]?.id, '0009-child-result')
assert.equal(summary[0]?.content, project0009CtoResult)
assert.equal(stableMessageTimelineKey(summary[0]!), 'result:source-task:cto-task-0009')
}
const sameRoleText = 'The role completed its independent architecture analysis and committed the result.'
const multiRoleSummary = selectCompanySummaryMessages([
resultMessage(
'0009-cto-role-result',
'session:company-cto',
sameRoleText,
{
source: 'engine',
transcript_kind: 'company_role_result',
work_item_projection_id: '0009-cto-work-item',
},
'assistant',
),
resultMessage(
'0009-coo-role-result',
'session:company-coo',
sameRoleText,
{
source: 'engine',
transcript_kind: 'company_role_result',
work_item_projection_id: '0009-coo-work-item',
},
'assistant',
),
], 'session:company-root')
assert.deepEqual(
multiRoleSummary.map(message => stableMessageTimelineKey(message)).sort(),
[
'result:work-item:0009-coo-work-item',
'result:work-item:0009-cto-work-item',
],
)
const sharedConversationTurn = 'shared-company-conversation-turn'
const parallelRoleResults = [
resultMessage(
'parallel-cto-result',
'session:company-cto',
'CTO completed the architecture assessment.',
{
source: 'engine',
transcript_kind: 'company_role_result',
canonical_turn_id: sharedConversationTurn,
work_item_projection_id: 'architecture-assessment',
},
'assistant',
),
resultMessage(
'parallel-coo-result',
'session:company-coo',
'COO completed the feature assessment.',
{
source: 'engine',
transcript_kind: 'company_role_result',
canonical_turn_id: sharedConversationTurn,
work_item_projection_id: 'feature-assessment',
},
'assistant',
),
]
assert.deepEqual(
parallelRoleResults.map(resultSurfaceDedupeKey),
[
'result:work-item:architecture-assessment',
'result:work-item:feature-assessment',
],
'parallel committed roles must use work-item identity before a shared conversation turn',
)
assert.equal(
selectCompanySummaryMessages(parallelRoleResults, 'session:company-root').length,
2,
)
const versionedSourceResult = resultMessage(
'versioned-source-result',
'session:company-child',
'Versioned result.',
{
source: 'engine',
transcript_kind: 'company_role_result_retry',
source_task_id: 'source-task-versioned',
retry_count: 2,
delivery_revision: 4,
} as ChatMessage['metadata'],
'assistant',
)
assert.equal(
stableResultDeliveryKey(versionedSourceResult),
'source-task:source-task-versioned:attempt:2:revision:4',
)
const fullEqualPriorityResult = {
...resultMessage(
'full-equal-priority',
'session:company-child-a',
'The complete authoritative body includes every required architectural conclusion and its supporting rationale.',
{
source: 'engine',
transcript_kind: 'company_role_result',
result_delivery_id: 'deterministic-delivery',
},
'assistant',
),
timestamp: 2_100,
}
const truncatedEqualPriorityResult = {
...resultMessage(
'truncated-equal-priority',
'session:company-child-b',
'supporting rationale.',
{
source: 'engine',
transcript_kind: 'company_role_result',
result_delivery_id: 'deterministic-delivery',
},
'assistant',
),
timestamp: 2_200,
}
for (const groups of [
[[fullEqualPriorityResult], [truncatedEqualPriorityResult]],
[[truncatedEqualPriorityResult], [fullEqualPriorityResult]],
]) {
const merged = mergeConversationMessages(groups)
assert.equal(merged.length, 1)
assert.equal(merged[0]?.id, 'full-equal-priority')
assert.equal(merged[0]?.content, fullEqualPriorityResult.content)
assert.equal(merged[0]?.timestamp, 2_100)
assert.equal(
stableMessageTimelineKey(merged[0]!),
'result:delivery:deterministic-delivery',
)
const replayed = mergeConversationMessages([
merged,
[truncatedEqualPriorityResult],
])
assert.equal(replayed.length, 1)
assert.equal(replayed[0]?.id, 'full-equal-priority')
assert.equal(replayed[0]?.content, fullEqualPriorityResult.content)
}
const equalLengthStableWinner = {
...resultMessage(
'z-stable-winner',
'session:company-child-a',
'BBBB',
{
source: 'engine',
transcript_kind: 'company_role_result',
result_delivery_id: 'equal-length-delivery',
},
'assistant',
),
timestamp: 3_200,
}
const equalLengthLoser = {
...resultMessage(
'a-stable-loser',
'session:company-child-b',
'AAAA',
{
source: 'engine',
transcript_kind: 'company_role_result',
result_delivery_id: 'equal-length-delivery',
},
'assistant',
),
timestamp: 3_100,
}
for (const groups of [
[[equalLengthStableWinner], [equalLengthLoser]],
[[equalLengthLoser], [equalLengthStableWinner]],
]) {
const firstMerge = mergeConversationMessages(groups)
assert.equal(firstMerge[0]?.id, 'z-stable-winner')
assert.equal(firstMerge[0]?.content, 'BBBB')
assert.equal(firstMerge[0]?.timestamp, 3_100)
const replayed = mergeConversationMessages([firstMerge, [equalLengthLoser]])
assert.equal(replayed[0]?.id, 'z-stable-winner')
assert.equal(replayed[0]?.content, 'BBBB')
}
assert.equal(companyHeaderView?.status, 'running')
assert.equal(companyHeaderView?.contextTokens, 0)
assert.equal(companyHeaderView?.contextWindow, 128000)
@@ -2,7 +2,7 @@ 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'
import { stableMessageTimelineKey, stableResultDeliveryKey } 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
@@ -20,22 +20,32 @@ 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
function normalizeResultContentFallback(content: string): string {
let normalized = String(content || '')
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.split('\n')
.map(line => line.trimEnd())
.join('\n')
.replace(/\n{3,}/g, '\n\n')
.trim()
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
// Some result mirrors add one explicit Markdown narrative label. Only
// remove that anchored wrapper; a colon later in Markdown (for example
// "**Work Item 1: ..." or "- ID: ...") is message content, not a title.
for (;;) {
const markdownTitle = normalized.match(/^\*\*([^\r\n]{8,160}?)\*\*:(?:[ \t]+|\r?\n+)([\s\S]+)$/)
if (!markdownTitle) break
const body = markdownTitle[2].trim()
if (body.length < 80 || body === normalized) break
normalized = body
}
const paragraphs = normalized.split(/\n{2,}/).map(part => part.trim()).filter(Boolean)
if (paragraphs.length > 1 && /^Verification:\s/i.test(paragraphs[paragraphs.length - 1])) {
normalized = paragraphs.slice(0, -1).join('\n\n').trim()
}
return compactWhitespace(normalized).slice(0, 2000)
}
function resultSurfacePriority(message: ChatMessage): number {
@@ -67,9 +77,52 @@ function resultSurfacePriority(message: ChatMessage): number {
return 0
}
function normalizedResultContentLength(message: ChatMessage): number {
return String(message.content ?? '')
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.split('\n')
.map(line => line.trimEnd())
.join('\n')
.trim()
.length
}
function resultAuthorityScore(message: ChatMessage): number {
const metadata = (message.metadata ?? {}) as Record<string, unknown>
let score = 0
if (String(metadata.source ?? '').trim() === 'engine') score += 4
if (String(metadata.source_result_message_id ?? '').trim()) score += 2
if (metadata.authoritative_output === true || metadata.canonical_result === true) score += 1
return score
}
function compareResultSurfacePreference(left: ChatMessage, right: ChatMessage): number {
const numericComparisons: Array<[number, number]> = [
[resultSurfacePriority(left), resultSurfacePriority(right)],
[resultAuthorityScore(left), resultAuthorityScore(right)],
[normalizedResultContentLength(left), normalizedResultContentLength(right)],
]
for (const [leftValue, rightValue] of numericComparisons) {
if (leftValue !== rightValue) return leftValue > rightValue ? 1 : -1
}
const idComparison = left.id.localeCompare(right.id)
if (idComparison !== 0) return idComparison
const contentComparison = left.content.localeCompare(right.content)
if (contentComparison !== 0) return contentComparison
// Result chronology is later rewritten to the earliest underlying delivery.
// Timestamp is therefore safe only after immutable identity/content have
// tied; it must never be able to flip the selected surface on replay.
if (left.timestamp === right.timestamp) return 0
return left.timestamp > right.timestamp ? 1 : -1
}
export function resultSurfaceDedupeKey(message: ChatMessage): string {
if (resultSurfacePriority(message) <= 0) return ''
const content = compactWhitespace(stripNarrativeTitlePrefix(message.content)).slice(0, 2000)
const deliveryKey = stableResultDeliveryKey(message)
if (deliveryKey) return `result:${deliveryKey}`
const content = normalizeResultContentFallback(message.content)
return content ? `result:${content}` : ''
}
@@ -492,7 +545,7 @@ export function mergeConversationMessages(messageGroups: ChatMessage[][]): ChatM
const existingIndex = resultKeyIndex.get(resultKey)
if (existingIndex !== undefined) {
const existing = merged[existingIndex]
const candidateWins = resultSurfacePriority(message) > resultSurfacePriority(existing)
const candidateWins = compareResultSurfacePreference(message, existing) > 0
const preferred = candidateWins ? message : existing
const secondary = candidateWins ? existing : message
merged[existingIndex] = {
@@ -505,7 +558,7 @@ export function mergeConversationMessages(messageGroups: ChatMessage[][]): ChatM
metadata: {
...(secondary.metadata ?? {}),
...(preferred.metadata ?? {}),
ui_timeline_id: stableMessageTimelineKey(existing),
ui_timeline_id: resultKey || stableMessageTimelineKey(existing),
},
}
continue
@@ -541,22 +594,6 @@ export function selectCompanySummaryMessages(
messages: ChatMessage[],
parentChannelId: string,
): ChatMessage[] {
const terminalAssistantTurn = (message: ChatMessage): string => {
const metadata = (message.metadata ?? {}) as Record<string, unknown>
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<string, ChatMessage>()
const durableMessages: ChatMessage[] = []
for (const message of messages) {
if (message.channelId === parentChannelId) {
@@ -584,38 +621,8 @@ export function selectCompanySummaryMessages(
'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])
}