@ -783,6 +783,8 @@ export const taskPollingMethods = {
this . handleSystemMessage ( eventData , eventIdx ) ;
break ;
case 'runtime_queue_sync' :
// 从头重建期间跳过 runtime_queue_sync, 避免与已加载的历史冲突导致 UI 闪回卡死
if ( this . _rebuildingFromScratch ) break ;
this . handleRuntimeQueueSync ( eventData ) ;
break ;
@ -1905,6 +1907,11 @@ export const taskPollingMethods = {
// 目标模式:运行中进度
handleGoalProgress ( data : any ) {
// 安全检查:确保目标事件属于当前对话,防止切换对话后旧任务事件污染
if ( data ? . conversation_id && this . currentConversationId && data . conversation_id !== this . currentConversationId ) {
debugLog ( '[Goal] 忽略不匹配对话的目标进度事件' , { eventCid : data.conversation_id , currentCid : this.currentConversationId } ) ;
return ;
}
debugLog ( '[Goal] 目标进度更新' , data ) ;
const status = String ( data ? . status || 'running' ) . toLowerCase ( ) ;
this . goalRunning = status === 'running' ;
@ -1958,6 +1965,11 @@ export const taskPollingMethods = {
// 目标模式:目标达成
handleGoalCompleted ( data : any ) {
// 安全检查:确保目标事件属于当前对话
if ( data ? . conversation_id && this . currentConversationId && data . conversation_id !== this . currentConversationId ) {
debugLog ( '[Goal] 忽略不匹配对话的目标完成事件' , { eventCid : data.conversation_id , currentCid : this.currentConversationId } ) ;
return ;
}
debugLog ( '[Goal] 目标已达成' , data ) ;
this . goalRunning = false ;
this . goalModeArmed = false ;
@ -1968,6 +1980,11 @@ export const taskPollingMethods = {
// 目标模式:目标停止(空转/轮数/token/用户取消)
handleGoalStopped ( data : any ) {
// 安全检查:确保目标事件属于当前对话
if ( data ? . conversation_id && this . currentConversationId && data . conversation_id !== this . currentConversationId ) {
debugLog ( '[Goal] 忽略不匹配对话的目标停止事件' , { eventCid : data.conversation_id , currentCid : this.currentConversationId } ) ;
return ;
}
debugLog ( '[Goal] 目标已停止' , data ) ;
this . goalRunning = false ;
this . goalModeArmed = false ;
@ -2721,54 +2738,57 @@ export const taskPollingMethods = {
// 2. 最后一条是空的 assistant 消息
// 3. 事件数量远大于历史中的 actions 数量(说明历史不完整)
// 4. 任务正处于文本流式阶段(刷新时易丢分段内容,强制重放全部事件)
const historyActionsCount = lastMessage ? . actions ? . length || 0 ;
const eventCount = allEvents . length ;
const historyIncomplete = eventCount > historyActionsCount + 5 ; // 允许5个事件的误差
const forceRebuildForStreamingText = inText || hasTextChunkEvent ;
const forceRebuildForStreamingText = inText || hasTextChunkEvent || inThinking ;
// 刷新恢复场景下,优先强制从头重放当前任务事件,避免“有运行中任务但界面无输出”
const forceRebuildOnRefresh = true ;
// 检查 assistant 消息的 actions 是否有仍在进行中的
const hasInProgressActions = isAssistantMessage && Array . isArray ( lastMessage . actions ) && lastMessage . actions . some ( ( action : any ) = > {
if ( ! action ) return false ;
if ( action . streaming ) return true ;
if ( action . type === 'tool' && action . tool ) {
const status = String ( action . tool . status || '' ) . toLowerCase ( ) ;
if ( [ 'preparing' , 'running' , 'pending' , 'queued' ] . includes ( status ) ) return true ;
}
return false ;
} ) ;
// 仅当确实需要时才重建,避免引导消息等场景下不必要的 assistant 消息移除和事件重放
const needsRebuild =
forceRebuildOnRefresh ||
! isAssistantMessage ||
( isAssistantMessage && ( ! lastMessage . actions || lastMessage . actions . length === 0 ) ) ||
historyIncomplete ||
forceRebuildForStreamingText ;
forceRebuildForStreamingText ||
hasInProgressActions ;
restoreDebugLog ( 'restore:rebuild-decision' , {
needsRebuild ,
forceRebuildOnRefresh ,
isAssistantMessage ,
historyActionsCount ,
eventCount ,
hasAssistantResponseEvent ,
hasAssistantContentEvent ,
historyIncomplete ,
inText ,
hasTextChunkEvent ,
forceRebuildForStreamingText
inThinking ,
forceRebuildForStreamingText ,
hasInProgressActions
} ) ;
if ( needsRebuild ) {
// if (historyIncomplete) {
// console.log('[TaskPolling] 历史不完整,从头重建:', {
// historyActionsCount,
// eventCount,
// diff: eventCount - historyActionsCount
// });
// }
debugLog ( '[TaskPolling] 需要从头重建 assistant 响应' ) ;
// 清空所有消息,准备从头重建
// 保留用户消息,只删除最后的 assistant 消息
// 关键修复:不要 pop assistant 消息,而是清空其 actions 并设置标记,
// 让 handleAiMessageStart 检测到 isRefreshRestore 并复用现有消息容器。
// 这样避免视觉闪回(消息不会突然消失)+ 新事件通过轮询增量追加,不会一次性同步处理几百个事件导致卡死。
if ( isAssistantMessage ) {
debugLog ( '[TaskPolling] 删除不完整的 assistant 消息' ) ;
this . messages . pop ( ) ;
debugLog ( '[TaskPolling] 复用现有 assistant 消息容器,清空 actions 等待事件重建' ) ;
lastMessage . actions = [ ] ;
lastMessage . awaitingFirstContent = true ;
lastMessage . generatingLabel = lastMessage . generatingLabel || '思考中...' ;
// 清理旧的流式标记,确保新事件能正确设置
if ( typeof lastMessage . streaming === 'boolean' ) {
lastMessage . streaming = true ;
}
}
this . streamingMessage = true ;
this . taskInProgress = true ;
if ( ! hasAssistantContentEvent ) {
if ( ! hasAssistantContentEvent && ! isAssistantMessage ) {
this . ensureRunningAssistantPlaceholder ? . ( runningTask , 'restore:no-visible-content-yet' ) ;
}
this . $forceUpdate ( ) ;