fix(ui-conversation): skip uncorrelated legacy events

This commit is contained in:
imccyu
2026-08-09 20:23:58 +08:00
parent 0cfe852c2d
commit 81822f4076
3 changed files with 53 additions and 3 deletions

View File

@@ -42,10 +42,13 @@ export const retryDefinition: ConversationNodeDefinition<RetryState> = {
kind: 'model-retry',
match: (event) => {
if (event.type === 'llm/retry') {
return { id: String(event.data.retryId), role: event.data.retry === 1 ? 'start' : 'update' }
const retryId: unknown = event.data.retryId
if (typeof retryId !== 'string' || retryId === '') return null
return { id: retryId, role: event.data.retry === 1 ? 'start' : 'update' }
}
if (event.type === 'llm/retry-started') {
return { id: String(event.data.retryId), role: 'update' }
const retryId: unknown = event.data.retryId
return typeof retryId === 'string' && retryId !== '' ? { id: retryId, role: 'update' } : null
}
return null
},

View File

@@ -241,7 +241,10 @@ export const toolDefinition: ConversationNodeDefinition<ToolState> = {
return { id: String(event.data.message.source.callId), role: 'update' }
}
if (event.type === 'tool/code-dispatch-start' || event.type === 'tool/code-dispatch') {
return { id: String(event.data.rootCallId), role: 'update' }
const rootCallId: unknown = event.data.rootCallId
return typeof rootCallId === 'string' && rootCallId !== ''
? { id: rootCallId, role: 'update' }
: null
}
return null
},

View File

@@ -718,6 +718,50 @@ describe('built-in conversation node Definitions', () => {
expect(node(snapshot(value), 'compaction')).toBeUndefined()
})
it('ignores legacy retry and code-dispatch events without correlation ids', () => {
const value = assembler([
at(10, 'llm/retry', {
turn: 1,
step: 1,
provider: 'fake',
mode: 'normal',
policyKey: 'fake-normal',
retry: 1,
maxRetries: 2,
delayMs: 10,
failure: { code: 'TRANSPORT', message: 'first legacy retry' },
}),
at(11, 'llm/retry-started', { turn: 1, step: 1, retry: 1 }),
at(20, 'llm/retry', {
turn: 2,
step: 1,
provider: 'fake',
mode: 'normal',
policyKey: 'fake-normal',
retry: 1,
maxRetries: 2,
delayMs: 10,
failure: { code: 'TRANSPORT', message: 'second legacy retry' },
}),
at(30, 'tool/code-dispatch-start', {
parentCallId: 'root',
subCallId: 'child',
name: 'legacy-subcall',
arguments: {},
}),
at(31, 'tool/code-dispatch', {
parentCallId: 'root',
subCallId: 'child',
name: 'legacy-subcall',
arguments: {},
content: [],
}),
], true)
expect(node(snapshot(value), 'model-retry')).toBeUndefined()
expect(node(snapshot(value), 'tool-call')).toBeUndefined()
})
it('suppresses a turn error when the loaded tail contains only a later retry attempt', () => {
const value = assembler([
at(5, 'llm/retry', {