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
@@ -127,16 +127,37 @@ assert.match(
'runtime events carrying a non-empty tool_name must update stable displayTool (empty tool_name keeps the sticky last command)',
)
// 8. Assistant streaming drafts must disappear at real terminal boundaries.
// 8. A streaming draft is one mounted logical turn. Completion/checkpoint
// events cannot remove it before the matching persisted final is merged.
assert.match(
src,
/evt\.type === 'turn_completed' \|\| evt\.type === 'turn_failed' \|\| evt\.type === 'checkpoint_saved'/,
'runtime terminal/checkpoint events must clear task-mode Live Reply drafts',
/const turnId = resolveCanonicalTurnId\(data\) \|\| undefined/,
'runtime deltas must use the shared canonical-turn resolver',
)
assert.match(
src,
/detailHasFinalForDraft[\s\S]*runtime_v2_assistant[\s\S]*ss\.clearDraft\(detailTaskId\)/,
'session_detail backfill of the final runtime assistant turn must clear matching Live Reply drafts',
/const startsNewCanonicalTurn = evt\.type === 'turn_started'[\s\S]*turnId !== activeDraftTurnId[\s\S]*evt\.type === 'turn_failed' \|\| startsNewCanonicalTurn/,
'only failure or a genuinely new canonical turn may clear an uncommitted draft',
)
assert.doesNotMatch(
src,
/evt\.type === 'turn_completed'[^\n]*clearDraft|evt\.type === 'checkpoint_saved'[^\n]*clearDraft/,
'turn completion and checkpoint persistence must not clear a draft ahead of its final message',
)
assert.match(
src,
/detailHasFinalForDraft[\s\S]*terminalAssistantTurnId\(message\) === draftTurnId[\s\S]*ss\.clearDraft\(detailTaskId\)/,
'session_detail may clear only after merging the matching terminal assistant turn',
)
assert.match(
src,
/cs\.addMessageFromBackend\(mapped\)[\s\S]*terminalTurnId = terminalAssistantTurnId\(mapped\)[\s\S]*terminalTurnId === activeDraftTurnId[\s\S]*clearDraft\(taskId\)/,
'session_message/chat_new_message must merge a matching terminal final before clearing its draft',
)
assert.doesNotMatch(
src,
/mapped\.sender !== 'user'\)\s*\{\s*sessionStoreRef\.current\?\.clearDraft/,
'an unrelated non-user session message must never clear an active draft',
)
// 9. Summary/full pagination and transport-local failures have independent
+27 -19
View File
@@ -24,6 +24,7 @@ import { companyRuntimeControlPatchForBoardStatus } from './lib/sessionRuntime'
import { getExecutionTurnId } from './lib/workItemRuntimeIds'
import { normalizeSessionCompanyProfile, normalizeSessionExecMode } from './lib/sessionIdentity'
import { extractSessionRecruitmentByRole, sessionChannelId } from './lib/sessionRecruitment'
import { resolveCanonicalTurnId, terminalAssistantTurnId } from './lib/turnIdentity'
import { unassignAgent } from './game/map/OfficeStore'
import type { AgentAnimStatus, EmployeeAssignment, KanbanPhase, KanbanTask, RoleAggregatedStatus, RoleWorkItemSummary, Session, TaskPreferredAgent } from './types/kanban'
@@ -919,9 +920,11 @@ export default function App() {
const taskId = typeof data.task_id === 'string' ? data.task_id : ''
if (taskId) {
const isDeltaEvent = evt.type === 'assistant_delta' || evt.type === 'thinking_delta'
const bufferedDraftTurnId = pendingDeltaFlushRef.current.get(taskId)?.draftTurnId
// Store-write ordering guarantee: everything buffered for this
// task lands before a non-delta event (turn boundaries call
// clearDraft; the draft must be flushed first, not after).
// task lands before a non-delta event. A matching terminal
// message replaces the draft later; flushing here must not make
// a completion boundary remove the visible turn prematurely.
if (!isDeltaEvent) flushPendingDeltas(taskId)
const ss = sessionStoreRef.current
const bs = boardStoreRef.current
@@ -931,13 +934,10 @@ export default function App() {
: ''
const executionMode = typeof data.execution_mode === 'string' ? data.execution_mode : ''
const isTaskModeRuntime = executionMode === 'task_mode' || projectionId === 'task_mode_execution'
const turnId = typeof data.turn_id === 'string' && data.turn_id
? data.turn_id
: typeof data.canonical_turn_id === 'string' && data.canonical_turn_id
? data.canonical_turn_id
: typeof data.execution_turn_id === 'string' && data.execution_turn_id
? data.execution_turn_id
: undefined
// Drafts represent one logical assistant turn. The shared
// resolver deliberately prefers conversation identity over the
// iteration-scoped runtime turn id.
const turnId = resolveCanonicalTurnId(data) || undefined
const marksCompanyRuntime =
!!projectionId && projectionId !== 'task_mode_execution' && !isTaskModeRuntime
if (marksCompanyRuntime && !isDeltaEvent && existingSession?.isCompanyRuntime !== true) {
@@ -993,7 +993,14 @@ export default function App() {
entry.kanbanPatch = { ...entry.kanbanPatch, ...kanbanPatch }
scheduleDeltaFlush()
} else {
if (evt.type === 'turn_started' || evt.type === 'turn_completed' || evt.type === 'turn_failed' || evt.type === 'checkpoint_saved') {
const activeDraftTurnId = String(
bufferedDraftTurnId ?? existingSession?.draftTurnId ?? '',
).trim()
const startsNewCanonicalTurn = evt.type === 'turn_started'
&& !!turnId
&& !!activeDraftTurnId
&& turnId !== activeDraftTurnId
if (evt.type === 'turn_failed' || startsNewCanonicalTurn) {
ss?.clearDraft(taskId)
}
ss?.updateSession(taskId, runtimePartial)
@@ -1084,14 +1091,9 @@ export default function App() {
payload.client_history_page === true,
)
const draftTurnId = String(existingSession?.draftTurnId ?? '').trim()
const detailHasFinalForDraft = !!draftTurnId && detailMessages.some((message) => {
if (message.sender === 'user') return false
const metadata = message.metadata ?? {}
const transcriptKind = String(metadata.transcript_kind ?? metadata.kind ?? '').trim()
if (transcriptKind !== 'runtime_v2_assistant') return false
const messageTurnId = String(metadata.canonical_turn_id ?? metadata.turn_id ?? '').trim()
return messageTurnId === draftTurnId
})
const detailHasFinalForDraft = !!draftTurnId
&& !!cs
&& detailMessages.some(message => terminalAssistantTurnId(message) === draftTurnId)
if (detailHasFinalForDraft) {
ss.clearDraft(detailTaskId)
}
@@ -1672,8 +1674,14 @@ export default function App() {
console.debug('[onSessionMessage]', mapped.sender, mapped.channelId, mapped.content?.slice(0, 60))
cs.addMessageFromBackend(mapped)
const taskId = mapped.channelId.startsWith('session:') ? mapped.channelId.slice('session:'.length) : ''
const terminalTurnId = terminalAssistantTurnId(mapped)
const activeDraftTurnId = String(
sessionStoreRef.current?.sessions.find(session => session.taskId === taskId)?.draftTurnId ?? '',
).trim()
if (taskId && mapped.sender !== 'user') {
sessionStoreRef.current?.clearDraft(taskId)
if (terminalTurnId && terminalTurnId === activeDraftTurnId) {
sessionStoreRef.current?.clearDraft(taskId)
}
// Force refresh — session messages are critical content that must sync
scheduleSessionDetailRefresh(taskId, 'full', true)
}
@@ -241,6 +241,153 @@ assert.equal(repeatedNativeCompanySync.length, 1)
assert.equal(repeatedNativeCompanySync[0].metadata?.ui_timeline_id, 'message:native-raw-1')
assert.equal(repeatedNativeCompanySync[0].timestamp, 5)
const structuredDeliveryMerge = __chatStoreTestUtils.dedupeMessages([
{
...nativeCompanyRawTurn,
content: 'Raw runtime wording before the canonical result wrapper.',
metadata: {
...nativeCompanyRawTurn.metadata,
result_delivery_id: 'result:company-turn-1:attempt:0',
},
},
{
...companyRoleResult,
content: 'Canonical committed wording may differ without creating another row.',
metadata: {
...companyRoleResult.metadata,
result_delivery_id: 'result:company-turn-1:attempt:0',
},
},
])
assert.equal(structuredDeliveryMerge.length, 1)
assert.equal(structuredDeliveryMerge[0].id, 'role-result-1')
assert.equal(
structuredDeliveryMerge[0].content,
'Canonical committed wording may differ without creating another row.',
)
const deliveryMirrorWithLongerWrapper = __chatStoreTestUtils.dedupeMessages([
{
...nativeCompanyRawTurn,
content: 'Runtime wrapper that must not outrank the committed surface.\n\nCanonical result body.',
metadata: {
...nativeCompanyRawTurn.metadata,
result_delivery_id: 'result:company-turn-wrapper:attempt:0',
},
},
{
...companyRoleResult,
content: 'Canonical result body.',
metadata: {
...companyRoleResult.metadata,
result_delivery_id: 'result:company-turn-wrapper:attempt:0',
},
},
])
assert.equal(deliveryMirrorWithLongerWrapper.length, 1)
assert.equal(
deliveryMirrorWithLongerWrapper[0].content,
'Canonical result body.',
'delivery mirrors follow surface authority; suffix repair only applies to one persistent message id',
)
const ctoDispatchContent = `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, the work-item state machine, collaboration policy, and seat executor mechanisms.
**Work Item 2: External Multi-Agent Frameworks Architecture Research**
- ID: \`d0307208-6b95-44c1-9b51-6bf073bbdcef\`
- Owner: senior_engineer
Both work items are independent and can execute in parallel.`
const ctoCompanyFinal: ChatMessage = {
id: 'runtime-v2-company-assistant-final:cto-turn-9',
channelId: 'session:cto-work-item',
sender: 'cto',
senderName: 'CTO',
content: ctoDispatchContent,
timestamp: 20,
mentions: [],
metadata: {
source: 'engine',
transcript_kind: 'runtime_v2_company_assistant',
canonical_turn_id: 'cto-turn-9',
ui_message_id: 'runtime-v2-company-assistant-final:cto-turn-9',
},
}
const repeatedCtoSnapshot = __chatStoreTestUtils.dedupeMessages([
ctoCompanyFinal,
{ ...ctoCompanyFinal, metadata: { ...ctoCompanyFinal.metadata } },
])
assert.equal(repeatedCtoSnapshot.length, 1)
assert.equal(
repeatedCtoSnapshot[0].content,
ctoDispatchContent,
'comparison normalization must never peel Work Item, ID, or Owner fields from rendered content',
)
let repeatedCtoMerge = [ctoCompanyFinal]
for (let replay = 0; replay < 6; replay += 1) {
repeatedCtoMerge = __chatStoreTestUtils.mergeMessagesIntoExisting(
repeatedCtoMerge,
[{ ...ctoCompanyFinal, metadata: { ...ctoCompanyFinal.metadata } }],
)
assert.equal(repeatedCtoMerge.length, 1)
assert.equal(
repeatedCtoMerge[0].content,
ctoDispatchContent,
`identical MERGE replay ${replay + 1} must preserve the complete source text`,
)
}
const truncatedCtoContent = ctoDispatchContent.slice(
ctoDispatchContent.indexOf('OpenOPC Source Code Architecture Deep-Dive Analysis**'),
)
const truncatedCtoFinal: ChatMessage = {
...ctoCompanyFinal,
content: truncatedCtoContent,
}
let interleavedCtoReplay = [ctoCompanyFinal]
for (const replayedMessage of [
truncatedCtoFinal,
ctoCompanyFinal,
truncatedCtoFinal,
ctoCompanyFinal,
]) {
interleavedCtoReplay = __chatStoreTestUtils.mergeMessagesIntoExisting(
interleavedCtoReplay,
[{ ...replayedMessage, metadata: { ...replayedMessage.metadata } }],
)
assert.equal(interleavedCtoReplay.length, 1)
assert.equal(
interleavedCtoReplay[0].content,
ctoDispatchContent,
'a same-identity truncated cache replay must neither replace nor duplicate the complete message',
)
}
const repairedCtoReplay = __chatStoreTestUtils.mergeMessagesIntoExisting(
[truncatedCtoFinal],
[ctoCompanyFinal],
)
assert.equal(repairedCtoReplay.length, 1)
assert.equal(repairedCtoReplay[0].content, ctoDispatchContent)
assert.equal(
__chatStoreTestUtils.mergeMessagesIntoExisting(repairedCtoReplay, [truncatedCtoFinal])[0].content,
ctoDispatchContent,
'once a complete same-identity source arrives, later truncated replays must not regress it',
)
const mountedHighPriorityResult: ChatMessage = {
...companyRoleResult,
id: 'mounted-high-result',
@@ -23,8 +23,8 @@ function messageMetadata(message: ChatMessage): Record<string, unknown> {
return (message.metadata ?? {}) as Record<string, unknown>
}
function normalizeMessageContent(content: string): string {
const normalized = String(content ?? '')
function normalizeMessageFormatting(content: string): string {
return String(content ?? '')
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.split('\n')
@@ -32,6 +32,10 @@ function normalizeMessageContent(content: string): string {
.join('\n')
.replace(/\n{3,}/g, '\n\n')
.trim()
}
function normalizeMessageContent(content: string): string {
const normalized = normalizeMessageFormatting(content)
const titleStripped = stripNarrativeTitlePrefix(normalized)
const paragraphs = titleStripped.split(/\n{2,}/).map(part => part.trim()).filter(Boolean)
if (paragraphs.length > 1 && /^Verification:\s/i.test(paragraphs[paragraphs.length - 1])) {
@@ -41,21 +45,40 @@ function normalizeMessageContent(content: string): string {
}
function stripNarrativeTitlePrefix(content: string): string {
const trimmed = String(content || '').trim()
const markdownTitle = trimmed.match(/^\*\*(.{8,160}?)\*\*:\s+([\s\S]+)$/)
if (markdownTitle) {
let trimmed = String(content || '').trim()
// Only an explicit, first-line "**Title**: body" wrapper is removable.
// Searching for an arbitrary `: ` in the first N characters is destructive:
// ordinary Markdown such as "**Work Item 1: ...**" and list fields such as
// "- ID: ..." would be peeled one layer at a time on repeated syncs.
for (;;) {
const markdownTitle = trimmed.match(/^\*\*([^\r\n]{8,160}?)\*\*:(?:[ \t]+|\r?\n+)([\s\S]+)$/)
if (!markdownTitle) return trimmed
const body = markdownTitle[2].trim()
if (body.length >= 80) return body
if (body.length < 80 || body === trimmed) return trimmed
trimmed = 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 losslessIdentityContent(
existing: ChatMessage,
candidate: ChatMessage,
preferred: ChatMessage,
sharesConcreteIdentity: boolean,
): string {
if (!sharesConcreteIdentity) return preferred.content
const existingComparable = normalizeMessageFormatting(existing.content)
const candidateComparable = normalizeMessageFormatting(candidate.content)
if (!existingComparable || !candidateComparable || existingComparable === candidateComparable) {
return preferred.content
}
// A cache/detail replay can contain a prefix-truncated copy of the same
// persistent message. Preserve the lossless source regardless of arrival
// order; unrelated edits still follow the normal preference rules.
if (existingComparable.endsWith(candidateComparable)) return existing.content
if (candidateComparable.endsWith(existingComparable)) return candidate.content
return preferred.content
}
function messageIdentityKeys(message: ChatMessage): Set<string> {
@@ -66,6 +89,9 @@ function messageIdentityKeys(message: ChatMessage): Set<string> {
for (const value of [
message.id,
typeof metadata.ui_message_id === 'string' ? metadata.ui_message_id : '',
typeof metadata.result_delivery_id === 'string' && metadata.result_delivery_id.trim()
? `delivery:${metadata.result_delivery_id.trim()}`
: '',
checkpointType && checkpointId ? `checkpoint:${checkpointType}:${checkpointId}` : '',
]) {
const normalized = String(value ?? '').trim()
@@ -81,7 +107,7 @@ function scopedMessageIdentityKeys(message: ChatMessage): Set<string> {
}
function isDerivedIdentityKey(value: string): boolean {
return value.startsWith('checkpoint:')
return value.startsWith('checkpoint:') || value.startsWith('delivery:')
}
function messageTimestamp(message: ChatMessage): number {
@@ -229,18 +255,17 @@ function mergeDuplicateMessages(
const existingIds = messageIdentityKeys(existing)
const candidateIds = messageIdentityKeys(candidate)
let sharesConcreteIdentity = false
let canonicalId = ''
for (const id of existingIds) {
if (candidateIds.has(id) && !isDerivedIdentityKey(id)) {
canonicalId = id
break
if (!candidateIds.has(id)) continue
if (!isDerivedIdentityKey(id)) {
sharesConcreteIdentity = true
if (!canonicalId) canonicalId = id
}
}
const normalizedContent = normalizeMessageContent(preferred.content)
const content = normalizedContent && normalizedContent === normalizeMessageContent(secondary.content)
? normalizedContent
: preferred.content
const content = losslessIdentityContent(existing, candidate, preferred, sharesConcreteIdentity)
const existingCheckpointId = String(messageMetadata(existing).checkpoint_id ?? '').trim()
const candidateCheckpointId = String(messageMetadata(candidate).checkpoint_id ?? '').trim()
@@ -17,6 +17,16 @@ assert.match(
assert.match(messageListSource, /scrollPolicy = 'follow'/, 'main transcript behavior should default to follow mode')
assert.doesNotMatch(messageListSource, /useVirtualizer/, 'chat transcript must use stable normal DOM rows')
assert.doesNotMatch(messageListSource, /PROGRAMMATIC_SCROLL_GRACE_MS/, 'scroll behavior must not regress to timer-based intent guessing')
assert.doesNotMatch(
messageListSource,
/seenNarrativeMessages|seenProjectUpdates/,
'MessageList must not independently remove durable rows using rendered content',
)
assert.match(
messageListSource,
/terminalAssistantTurnId\(message\)/,
'draft suppression must use the shared committed-turn resolver',
)
const progressWithoutServerId = {
type: 'status_change' as const,
@@ -147,7 +157,16 @@ const companyTurnKeys = [
metadata: { kind: 'company_role_result', canonical_turn_id: sharedCompanyTurn },
}),
]
assert.equal(new Set(companyTurnKeys).size, companyTurnKeys.length, 'distinct company rows sharing a turn need unique DOM keys')
assert.equal(
companyTurnKeys[1],
companyTurnKeys[2],
'company draft/final surfaces for one canonical turn must reuse the same DOM slot',
)
assert.notEqual(
companyTurnKeys[2],
companyTurnKeys[3],
'a separately committed role result keeps its result-delivery identity',
)
const narrativeItems = buildNarrativeMessageItems([
baseMessage('m1', '[Company:cto::execute::abc] starting Research source reliability', 1000),
@@ -163,35 +182,63 @@ assert.equal(narrativeItems[0].kind === 'ops-bundle' ? narrativeItems[0].events.
assert.equal(narrativeItems[1].kind, 'message')
assert.equal(narrativeItems[2].kind, 'ops-bundle')
const longResult = 'Completed the focused recheck and produced the QA artifact with caveats for downstream aggregation.'
const dedupedProjectUpdates = buildNarrativeMessageItems([
baseMessage('u1', prefixedPayload, 2000, 'qa_analyst'),
baseMessage('u2', `**Report #1: Recheck remediated screen**: ${prefixedPayload}`, 2000, 'qa_analyst'),
], { isCompanyRuntime: true, detailMode: 'summary' })
assert.equal(dedupedProjectUpdates.length, 1)
assert.equal(
dedupedProjectUpdates.length,
2,
'renderer must preserve distinct project-update rows; upstream identity owns consolidation',
)
assert.equal(dedupedProjectUpdates[0].kind, 'message')
assert.equal(dedupedProjectUpdates[0].kind === 'message' ? dedupedProjectUpdates[0].msg.id : '', 'u1')
const longResult = 'Completed the focused recheck and produced the QA artifact with caveats for downstream aggregation.'
const dedupedNarrativeMessages = buildNarrativeMessageItems([
const identicalDurableNarratives = buildNarrativeMessageItems([
baseMessage('same-content-1', longResult, 2500, 'qa_analyst'),
baseMessage('same-content-2', longResult, 2500, 'qa_analyst'),
], { isCompanyRuntime: true, detailMode: 'summary' })
assert.deepEqual(
identicalDurableNarratives.map(item => item.kind === 'message' ? item.msg.id : item.id),
['same-content-1', 'same-content-2'],
'distinct stable identities must survive even when sender, timestamp, and content are identical',
)
const ambiguousNarrativeMessages = buildNarrativeMessageItems([
baseMessage('n1', longResult, 3000, 'qa_analyst'),
baseMessage('n2', `Recheck remediated ten-bagger candidate screen: ${longResult}`, 3000, 'qa_analyst'),
], { isCompanyRuntime: true, detailMode: 'summary' })
assert.equal(dedupedNarrativeMessages.length, 1)
assert.equal(dedupedNarrativeMessages[0].kind === 'message' ? dedupedNarrativeMessages[0].msg.id : '', 'n1')
assert.equal(
ambiguousNarrativeMessages.length,
2,
'plain narrative prefixes are content and must not be stripped to guess message identity',
)
const duplicatedResultSurface = buildNarrativeMessageItems([
{
...baseMessage('r1', longResult, 4000, 'chao'),
metadata: { source: 'engine', transcript_kind: 'child_task_result' },
metadata: {
source: 'engine',
transcript_kind: 'child_task_result',
result_delivery_id: 'delivery-r1',
},
},
{
...baseMessage('r2', `Deliver final result to user: ${longResult}`, 4500, 'system'),
senderName: 'Company Member',
metadata: { source: 'runtime_event', kind: 'worker_notification', notification_kind: 'task_complete' },
metadata: {
source: 'engine',
transcript_kind: 'child_result',
result_delivery_id: 'delivery-r1',
},
},
], { isCompanyRuntime: true, detailMode: 'summary' })
assert.equal(duplicatedResultSurface.length, 1)
assert.equal(duplicatedResultSurface[0].kind === 'message' ? duplicatedResultSurface[0].msg.id : '', 'r1')
assert.equal(
duplicatedResultSurface.length,
2,
'MessageList must not run a second result consolidator after the store/company projection',
)
const fullItems = buildNarrativeMessageItems([
baseMessage('m1', '[Company:cto::execute::abc] starting Research source reliability', 1000),
@@ -4,6 +4,7 @@ import type { ProgressEntry, RoleWorkItemSummary, Session, WorkItemProgressEntry
import { progressEntryKey } from '../lib/progressEntryKey'
import { stableMessageTimelineKey } from '../lib/messageTimelineIdentity'
import { isMessageVisibleAtDetailLevel, resultSurfaceDedupeKey } from '../lib/workItemSessions'
import { resolveCanonicalTurnId, terminalAssistantTurnId } from '../lib/turnIdentity'
import { IconCopy, IconCheck, IconChat, IconSparkle, IconShield, IconActivity, IconChevron } from './SvgIcons'
import { AgentProgressBlock, AgentProgressEntryCard, INLINE_PROGRESS_ENTRY_TYPES } from './AgentProgressBlock'
import { MarkdownBody } from './MarkdownBody'
@@ -345,13 +346,6 @@ export function messageTimelineKey(message: ChatMessage): string {
return stableMessageTimelineKey(message)
}
function terminalAssistantTurnId(message: ChatMessage): string {
const timelineKey = messageTimelineKey(message)
return timelineKey.startsWith('turn:assistant:')
? timelineKey.slice('turn:assistant:'.length)
: ''
}
function formatTime(ts: number) {
return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })
}
@@ -400,44 +394,6 @@ 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 isResultSurfaceMessage(message: ChatMessage): boolean {
const transcriptKind = String(message.metadata?.transcript_kind ?? message.metadata?.kind ?? '').trim()
if ([
'runtime_v2_assistant',
'runtime_v2_company_assistant',
'top_level_reply',
'company_role_result',
'company_role_result_retry',
'child_task_result',
'child_task_result_retry',
'child_result',
].includes(transcriptKind)) {
return true
}
if (String(message.metadata?.kind ?? '').trim() === 'worker_notification') {
return true
}
return false
}
function parseJsonObjectText(content: string): Record<string, unknown> | null {
const trimmed = String(content || '').trim()
if (!trimmed) return null
@@ -830,8 +786,6 @@ export function buildNarrativeMessageItems(
const items: TimelineItem[] = []
let bundle: SystemOpsBundleEvent[] = []
let bundleSortOrder = 0
const seenProjectUpdates = new Set<string>()
const seenNarrativeMessages = new Set<string>()
const flushBundle = () => {
if (bundle.length === 0) return
@@ -854,33 +808,6 @@ export function buildNarrativeMessageItems(
bundle.push(ops)
return
}
const projectUpdate = detailMode === 'summary' ? parseProjectUpdatePayload(msg.content) : null
if (projectUpdate) {
const dedupeKey = [
msg.sender,
Math.round(msg.timestamp / 1000),
projectUpdate.kind,
compactWhitespace(projectUpdate.summary || projectUpdate.acceptanceSummary || '').slice(0, 500),
projectUpdate.verdict ?? '',
projectUpdate.deliverables.map(item => `${item.name}:${item.path}`).join('|').slice(0, 800),
].join('\u0001')
if (seenProjectUpdates.has(dedupeKey)) return
seenProjectUpdates.add(dedupeKey)
}
if (detailMode === 'summary' && !isCheckpointCardMetadata(msg.metadata)) {
const canonicalContent = compactWhitespace(stripNarrativeTitlePrefix(msg.content)).slice(0, 1200)
if (canonicalContent) {
const dedupeKey = isResultSurfaceMessage(msg)
? ['result', canonicalContent].join('\u0001')
: [
msg.sender,
Math.round(msg.timestamp / 1000),
canonicalContent,
].join('\u0001')
if (seenNarrativeMessages.has(dedupeKey)) return
seenNarrativeMessages.add(dedupeKey)
}
}
flushBundle()
items.push({
kind: 'message',
@@ -1331,7 +1258,7 @@ export const MessageList = React.memo(function MessageList({
for (const message of timelineMessages) {
const thinking = String(message.metadata?.runtime_thinking ?? '').trim()
if (!thinking) continue
const turnId = String(message.metadata?.canonical_turn_id ?? message.metadata?.turn_id ?? '').trim()
const turnId = resolveCanonicalTurnId(message.metadata)
if (turnId && thinkingProgressTurnIds.has(turnId)) continue
entries.push({
type: 'thinking' as const,
@@ -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])
}
@@ -472,7 +472,7 @@ async function runTimelineIdentityCases(page: Page, baseUrl: string): Promise<vo
collapsed: !!committed.querySelector('.msg-collapse-toggle'),
}
})
assert.equal(finalizedDraft.reused, true, 'draft -> runtime_v2_assistant final must reuse the same outer timeline DOM node')
assert.equal(finalizedDraft.reused, true, 'draft -> runtime_v2_company_assistant final must reuse the same outer timeline DOM node')
assert.equal(finalizedDraft.collapsed, false, 'a mounted expanded draft must not auto-collapse when its final arrives')
assert.ok(
finalizedDraft.height >= draftGeometry.height - 4,
@@ -366,7 +366,8 @@ function Fixture() {
mentions: [],
metadata: {
canonical_turn_id: LIVE_TURN_ID,
transcript_kind: 'runtime_v2_assistant',
result_delivery_id: `result:${LIVE_TURN_ID}:attempt:0`,
transcript_kind: 'runtime_v2_company_assistant',
},
},
])
@@ -341,7 +341,13 @@ export interface ChatMessageMeta {
/** UI-only identity retained across semantic result-surface replacement. */
ui_timeline_id?: string
canonical_turn_id?: string
conversation_turn_id?: string
turn_id?: string
execution_turn_id?: string
result_delivery_id?: string
source_result_message_id?: string
source_task_id?: string
child_session_id?: string
execution_mode?: string
transcript_kind?: string
detail_visibility?: DetailVisibility