新增: 消息级溯源 P1 + 修复流式失败重试(UX-260619-06)

消息级溯源 P1(溯源字段升级):
- ContextManager 加 last_assistant/last_user_message_id 取消息 id
- audit 6 处 message_id 接真值(替换 P0 None 占位)
- 知识提炼 source_ref 升级 conv_msg:{id}(知识生命线 extracted/referenced 双事件)
- 4 处 build_knowledge_context 调用方传 user message id
- 老数据 id=None 降级 conv:{id} 兼容

流式失败重试修复(UX-260619-06):
- 症状1(回答一半消失):AiError 收尾前 flushCurrentText 保留部分回复到占位气泡
  (原仅 AiAgentRound/AiCompleted 回填,AiError 清 currentText 致部分回复丢失)
- 症状2(重试报"没有可重新生成的回复"):ai_regenerate pop_last_assistant_round 返 false 时
  末尾是 user(流式中途失败这轮 assistant 未持久化)直接从该 user 重跑(等价重发)
  1214 连续 user 已由 merge_consecutive_users(7c98134)修,本批补失败重试链路

自验: workspace EXIT 0 + cargo test 全绿 + vue-tsc EXIT 0
This commit is contained in:
2026-06-19 19:54:16 +08:00
parent cea0c71546
commit d3e7640b8d
7 changed files with 219 additions and 25 deletions

View File

@@ -126,11 +126,12 @@ export function friendlyError(raw: string): string {
return raw
}
/** 把流式累积的 currentText 回填到最后一条 assistant 消息(AiAgentRound/AiCompleted 收尾共用) */
/** 把流式累积的 currentText 回填到最后一条 assistant 消息(AiAgentRound/AiCompleted/AiError 收尾共用) */
export function flushCurrentText() {
if (!state.currentText) return
const last = state.messages[state.messages.length - 1]
if (last && last.role === 'assistant') last.content = state.currentText
// 跳过 isError 气泡(AiStreamRetry 错误气泡):避免部分回复覆盖错误提示
if (last && last.role === 'assistant' && !last.isError) last.content = state.currentText
}
/** token 用量展示开关(读 appSettings,与 Settings.vue 共享 key `df-show-token-usage`) */
@@ -395,6 +396,9 @@ function handleLifecycleEvent(event: AiChatEvent): boolean {
clearStreamWatchdog()
clearAllToolSlowTimers() // B-260616-12: 错误收尾清全部工具慢执行计时器与已提示集合
clearAllApprovalTimers() // UX-260617-10: 错误中断释放所有审批超时计时器,防回调改 state 触发已卸载/已错流程
// UX-260619-06: 失败前先把流式累积的 currentText 回填到占位 assistant 气泡,
// 保留"回答到一半"的部分回复(否则清 currentText 后部分内容消失)。
flushCurrentText()
state.streaming = false
state.generatingConvId = null
state.currentText = ''