修复: AI工具OOM/路径遍历+续跑锁收敛+UX交互批
This commit is contained in:
@@ -326,11 +326,15 @@ export function handleEvent(event: AiChatEvent) {
|
||||
case 'AiError': {
|
||||
clearStreamWatchdog()
|
||||
clearAllToolSlowTimers() // B-260616-12: 错误收尾清全部工具慢执行计时器与已提示集合
|
||||
clearAllApprovalTimers() // UX-260617-10: 错误中断释放所有审批超时计时器,防回调改 state 触发已卸载/已错流程
|
||||
state.streaming = false
|
||||
state.generatingConvId = null
|
||||
state.currentText = ''
|
||||
state.agentRound = 0 // AE-2025-07: agentic 异常中断,复位轮次
|
||||
state.queue = [] // B-32:错误收尾清队列,防生成中入队的消息被静默丢失(drainQueue 仅 AiCompleted 触发)
|
||||
// UX-260617-10: 错误收尾清残留待审批项——错误发生时若有工具停在 pending_approval,
|
||||
// 残留可点击审批按钮会让用户误以为还能批(实际后端已终止),残留审批卡误导操作。
|
||||
state.pendingApprovals = []
|
||||
pendingMaxRounds.value = false // F-260616-03: 异常中断,清操作卡
|
||||
localStorage.removeItem('df-ai-gen')
|
||||
localStorage.removeItem('df-ai-text')
|
||||
@@ -356,14 +360,23 @@ export async function startListener() {
|
||||
if (_unlistenAiEvent && _unlistenConvChanged && _unlistenApprovalClear) return
|
||||
// 并发去重:防 onMounted 与 sendMessage 首发竞态重复注册 listener → 文字双倍
|
||||
if (_startPromise) return _startPromise
|
||||
// UX-260617-09: 内部任一 await reject 时,onMounted/sendMessage 的 await 在 Vue async
|
||||
// 生命周期/void 调用点处成为未捕获 rejection(无 console 输出)→ listener 未注册且无任何提示,
|
||||
// 表现为「发消息永远不回」。这里 catch + console.error 保留可观测性,再 throw 让外层
|
||||
// finally 清 _startPromise(并发去重复位),调用方仍可 try/catch 兜底。
|
||||
_startPromise = (async () => {
|
||||
_unlistenAiEvent = await aiApi.onEvent(handleEvent)
|
||||
_unlistenConvChanged = await listen('ai-conversation-changed', () => { void loadConversations() })
|
||||
// AE-2025-06: onStreamTimeout(流式整流超时收尾)广播的清理事件 → 清全部审批超时计时器。
|
||||
// useAiStream 不 import useAiSend(避循环依赖),经事件总线中转。
|
||||
_unlistenApprovalClear = await listen('ai-approval-clear-timers', () => { clearAllApprovalTimers() })
|
||||
// B-260616-01: L0 握手 — 通知后端前端已就绪,后端据此清除 HMR/刷新导致的残留 generating 状态
|
||||
emit('ai-client-ready', { lastConvId: state.activeConversationId })
|
||||
try {
|
||||
_unlistenAiEvent = await aiApi.onEvent(handleEvent)
|
||||
_unlistenConvChanged = await listen('ai-conversation-changed', () => { void loadConversations() })
|
||||
// AE-2025-06: onStreamTimeout(流式整流超时收尾)广播的清理事件 → 清全部审批超时计时器。
|
||||
// useAiStream 不 import useAiSend(避循环依赖),经事件总线中转。
|
||||
_unlistenApprovalClear = await listen('ai-approval-clear-timers', () => { clearAllApprovalTimers() })
|
||||
// B-260616-01: L0 握手 — 通知后端前端已就绪,后端据此清除 HMR/刷新导致的残留 generating 状态
|
||||
emit('ai-client-ready', { lastConvId: state.activeConversationId })
|
||||
} catch (err) {
|
||||
console.error('[useAiEvents] startListener 注册失败,事件监听未生效:', err)
|
||||
throw err
|
||||
}
|
||||
})()
|
||||
try {
|
||||
await _startPromise
|
||||
@@ -382,6 +395,10 @@ function stopListener() {
|
||||
_unlistenAiEvent = null
|
||||
_unlistenConvChanged = null
|
||||
_unlistenApprovalClear = null
|
||||
// UX-260617-26: stop 清零去重 promise——极速 mount/unmount/mount(HMR)时,首 mount 的
|
||||
// _startPromise 可能仍在 pending(未 await 完即 unmount),再 mount 若命中并发去重分支会
|
||||
// 返回这个已代表「旧注册」的过期 promise,新 listener 实际未注册。
|
||||
_startPromise = null
|
||||
clearStreamWatchdog()
|
||||
clearAllToolSlowTimers() // B-260616-12: 卸载时释放所有工具慢执行计时器,防回调改 state 触发已卸载组件
|
||||
clearAllApprovalTimers() // AE-2025-06: 卸载时释放所有审批超时计时器,防回调 push 消息触发已卸载组件
|
||||
|
||||
Reference in New Issue
Block a user