新增: 跨端用户消息同步
This commit is contained in:
@@ -313,16 +313,23 @@ function handleStreamingEvent(event: AiChatEvent): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
case 'AiMaxRoundsReached':
|
||||
case 'AiMaxRoundsReached': {
|
||||
// F-260616-03: 达 max_iterations 暂停态(后端仍 generating=true),前端展示操作卡询问。
|
||||
// 后端已 save 落库,这里仅翻 pendingMaxRounds 驱动 UI 卡片;看门狗已由
|
||||
// NO_RESET_WATCHDOG 跳过 reset(达 max 不计整流超时)。
|
||||
// BUG-260623-06: NO_RESET 只跳 reset 不 clear,达 max 前活跃事件(delta/tool)设的 timer 仍走,
|
||||
// 130s 后触发 onStreamTimeout → generating=true(后端保持)+ tool completed → 误报
|
||||
// "工具已执行完成,后续回复中断"(实测用户报"对话完成后等一会弹出")。
|
||||
// 修:对齐 AiApprovalRequired/AiDirAuthRequired(等用户场景均 clear),达 max 也 clear watchdog。
|
||||
// 用户点继续(ai_continue_loop→后端续跑→delta reset)/停止(ai_stop_loop→AiCompleted clear)时重计。
|
||||
clearStreamWatchdog(event.conversation_id || undefined)
|
||||
// TD-260621-02 per-conv:存挂起 convId(非 boolean),消费方按 activeConversationId 精确比对。
|
||||
// 注:无 convId 时**不**设 null(=无挂起,达 max 操作卡丢失)——保留原值兜底。
|
||||
// 用 activeConversationId 兜底:达 max 事件理论上必带 convId,无时默认属当前活跃会话,
|
||||
// 优于清空挂起导致 MaxRoundsCard 守卫误判无挂起(用户达 max 操作卡凭空消失)。
|
||||
pendingMaxRounds.value = event.conversation_id || state.activeConversationId || pendingMaxRounds.value
|
||||
return true
|
||||
}
|
||||
|
||||
case 'AiDirAuthRequired': {
|
||||
// F-260619-03 Phase B: 路径授权挂起(后端 generating 保持 true,等用户决策)。
|
||||
@@ -528,6 +535,33 @@ function handleConvStateEvent(event: AiChatEvent): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
/** 跨端用户消息域:F-260622-02 miniapp→桌面 user 气泡同步。
|
||||
*
|
||||
* 仅处理 AiUserMessage 一个事件。语义:远程(miniapp)发来的用户消息经后端 publish_event
|
||||
* 广播到 ai_event_bus,桌面收到后补 user 气泡(桌面本地 sendMessage 是乐观渲染不发此事件,
|
||||
* 故桌面 store 不会有这条 user 气泡,需要此事件填补,否则桌面只看到孤立 assistant 响应气泡)。
|
||||
*
|
||||
* 去重(防双气泡):若末条消息已是同 content 的 user(理论上桌面本地已乐观 push 过,
|
||||
* 或 miniapp 自身乐观 push 后又被此事件回灌),跳过不重复 push。MVP 按末条 + content 比对
|
||||
* 足够覆盖单会话场景;极端情况下两条不同 user 恰巧同 content 紧邻不会丢(末条不同才 push)。
|
||||
*
|
||||
* 返回值:true=命中 AiUserMessage;false=其他事件。 */
|
||||
function handleUserMessageEvent(event: AiChatEvent): boolean {
|
||||
if (event.type !== 'AiUserMessage') return false
|
||||
// 去重:末条已是 user 且 content 相同则跳过(防桌面本地乐观 push + 事件回灌双气泡)。
|
||||
const last = state.messages[state.messages.length - 1]
|
||||
if (last && last.role === 'user' && last.content === event.message) {
|
||||
return true
|
||||
}
|
||||
state.messages.push({
|
||||
id: `user-${nextMsgId()}`,
|
||||
role: 'user',
|
||||
content: event.message,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
/** lifecycle 域:整轮收尾(AiCompleted)与异常中断(AiError) */
|
||||
function handleLifecycleEvent(event: AiChatEvent): boolean {
|
||||
switch (event.type) {
|
||||
@@ -710,6 +744,12 @@ export function handleEvent(event: AiChatEvent) {
|
||||
// 在 isCurrent 守卫**之前**处理,避免切走会话时 conv_state 不更新致停止按钮/MaxRoundsCard 错态。
|
||||
// 该事件非流式活跃信号,不重置看门狗、不进 generatingConvs 通用 add 逻辑(由 conv_state 值决定 add/delete)。
|
||||
if (handleConvStateEvent(event)) return
|
||||
// F-260622-02 跨端用户消息同步:在 isCurrent 守卫**之前**处理——AiUserMessage 是补 user 气泡
|
||||
// 信号(非流式活跃事件),不应触发 watchdog reset / generatingConvs add(后续 AiAgentRound/
|
||||
// AiTextDelta 等活跃事件会正常触发)。也不受"非当前会话"过滤——理论上此事件必带 conversation_id
|
||||
// 且属当前会话(后端 route_send_message 在放行时广播),即使因切走会话到达也仅补 user 气泡,
|
||||
// 不影响其他态(handleUserMessageEvent 去重幂等)。
|
||||
if (handleUserMessageEvent(event)) return
|
||||
// 事件不属于当前展示对话(生成中切走了)→ 不污染当前视图,仅完成/错误/求助时刷新侧边栏
|
||||
// L1 求助协议(§2.3):AiHelpRequired 同 AiError 后端已 guard.reset 终止 loop,需移出生成集。
|
||||
const isCurrent = !convId || convId === state.activeConversationId
|
||||
|
||||
Reference in New Issue
Block a user