新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展 - 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通 - 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦 - AI planner/plan_hint/intent:aichat B 路线并行多轮基础 - patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环 - 压缩超时兜底(F-15 卡死根治) - F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本 - 知识注入 DRY/skills/audit 扩展 清理: - aichat 技术债(误报 allow/死导入/过时注释 30 项) - URGENT.md 删除(11 项加急全解决/迁 todo) - 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
@@ -14,23 +14,43 @@
|
||||
import { state } from '@/stores/ai'
|
||||
import { t } from '@/i18n/i18n-helpers'
|
||||
import { nextMsgId } from './aiShared'
|
||||
import { forceResetStreaming } from './streamingGuard'
|
||||
import { emit } from '@tauri-apps/api/event'
|
||||
|
||||
/// ≥ 后端 STREAM_IDLE_TIMEOUT(120s, ai.rs:848)+余量;
|
||||
/// ≥ 后端 STREAM_IDLE_TIMEOUT(120s, stream_recv.rs STREAM_IDLE_TIMEOUT)+余量;
|
||||
/// 前端若短于后端,慢首 token 会被前端先误杀
|
||||
export const STREAM_TIMEOUT_MS = 130000
|
||||
|
||||
let _streamWatchdog: ReturnType<typeof setTimeout> | null = null
|
||||
// TD-260621-01 per-conv 看门狗:模块级单计时器 → convId → timer Map。
|
||||
//
|
||||
// 背景(F-09 多会话并发):原单 timer 下,A 会话 delta 重置计时器会顶掉 B 会话已挂起的 timer,
|
||||
// 或 A 超时连累 B 的 generatingConvs(全清)。per-conv Map 后,各会话独立计时,互不顶替/连累。
|
||||
//
|
||||
// 向后兼容(避撞 F-09 禁动域):convId 可选。禁动域 useAiSend.ts/useAiApproval.ts 的 14 处调用
|
||||
// 不传 convId,走 _legacyWatchdog fallback(模块级单 timer,行为对齐旧版);本批域 useAiEvents.ts
|
||||
// 的调用点(已知 convId)传 convId 走 per-conv Map。两路并存,等 F-09 统一迁移调用点后可删 fallback。
|
||||
//
|
||||
// per-conv 路径与 legacy 路径互不干扰:convId 路径只动 Map,legacy 路径只动 _legacyWatchdog。
|
||||
// 实际并发场景:F-09 已让 useAiEvents 按 conversation_id 路由事件(handleEvent:546 isCurrent 判定),
|
||||
// 非 current conv 的事件不触发 reset/clear(reset/clear 在 isCurrent 分支内调用),故 per-conv
|
||||
// 路径天然只跟踪当前展示会话的活跃 timer,跨会话切换时旧 conv 的 timer 由其 AiCompleted/AiError 精确清。
|
||||
const _watchdogTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
// 禁动域 fallback 单 timer(useAiSend/useAiApproval 无 convId 调用走此,行为对齐旧版)。
|
||||
let _legacyWatchdog: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
/** 看门狗超时回调:收尾 streaming 态并补错误消息 */
|
||||
export function onStreamTimeout() {
|
||||
state.streaming = false
|
||||
// F-09: 多会话并发 — 超时收尾清空全部生成态(整流超时是兜底场景,无法确定具体 conv,
|
||||
// 保守全清避免幽灵;正常路径由各 conv 的 AiCompleted/AiError 精确 remove)
|
||||
state.generatingConvs.clear()
|
||||
state.currentText = ''
|
||||
state.queue = [] // B-32:超时收尾同步清队列,防生成中入队的消息静默丢失
|
||||
clearStreamWatchdog()
|
||||
/**
|
||||
* 看门狗超时回调:收尾 streaming 态并补错误消息(走 forceResetStreaming 兜底复位,
|
||||
* 对齐 [[devflow-generating-statemachine]])。
|
||||
*
|
||||
* TD-260621-01 per-conv:携带 convId 时,forceResetStreaming(reason, convId) 仅清该 conv 的
|
||||
* generatingConvs(经 setStreaming 联动 delete),不再全清误杀并发会话生成态。convId 为空
|
||||
* (legacy fallback 路径)时不带 convId,generatingConvs 不动(全局兜底,行为对齐旧版)。
|
||||
*/
|
||||
export function onStreamTimeout(convId?: string) {
|
||||
// 卡死兜底走 guard 的 forceResetStreaming(复位 streaming + 清 currentText/queue + warn 日志)。
|
||||
// TD-260621-01:携带 convId 时仅清该 conv 的 generatingConvs(per-conv);不传时全局兜底不动 Set。
|
||||
forceResetStreaming('onStreamTimeout(130s 无数据)', convId)
|
||||
clearStreamWatchdog(convId)
|
||||
// AE-2025-06: 整流超时收尾 → 广播清全部审批超时计时器。
|
||||
// 本模块不 import useAiSend(会构成 useAiSend↔useAiStream 循环依赖,与现有破环原则冲突),
|
||||
// 复用 ai-tool-slow-toast 同款事件总线模式:emit 由 useAiEvents.startListener listen 后调
|
||||
@@ -44,16 +64,21 @@ export function onStreamTimeout() {
|
||||
// 2) 探测任一 completed 卡片以区分错误文案(工具已执行完后续回复中断 vs 纯流式中断)。
|
||||
// 反向扫尾部提前退出(同 findToolCall 策略);不复用 useAiEvents.findToolCall:本模块
|
||||
// 不依赖 useAiEvents(已下沉 nextMsgId 到 aiShared 破环),且此处需全量回滚而非按 id 定位。
|
||||
// 注:messages 遍历仍是单例(currentText 一样),per-conv 超时只切到当前 messages 视图;
|
||||
// 多会话并发下其他会话的 messages 不在此 state.messages(切走即整体替换),无跨会话误回滚。
|
||||
let hasCompletedTool = false
|
||||
for (let i = state.messages.length - 1; i >= 0; i--) {
|
||||
const toolCalls = state.messages[i].toolCalls
|
||||
if (!toolCalls) continue
|
||||
for (let j = 0; j < toolCalls.length; j++) {
|
||||
const tc = toolCalls[j]
|
||||
if (tc.status === 'running') {
|
||||
// B-33:running 态无后续事件回流,置 rejected 释放骨架屏,恢复重审入口
|
||||
tc.status = 'rejected'
|
||||
} else if (tc.status === 'completed') {
|
||||
// TD-260621-01 修复(用户实测卡死根因):不再把 running→rejected。
|
||||
// path_auth/risk 挂起的 toolCall 停在 running(path_auth 不置 pending_approval),
|
||||
// 看门狗超时自动拒绝会误杀用户未操作的审批(用户报"显示用户拒绝了此操作,但根本没操作")。
|
||||
// 看门狗只做流断兜底(清 streaming/currentText/queue),工具/审批态由各自生命周期
|
||||
// (AiToolCallCompleted/AiApprovalResult/ai_authorize_dir)管理。running 骨架屏由
|
||||
// ToolCard 本地 approving 计时器兜底,或 path_auth 授权后 AiToolCallCompleted 转 completed。
|
||||
if (tc.status === 'completed') {
|
||||
hasCompletedTool = true
|
||||
}
|
||||
}
|
||||
@@ -70,15 +95,48 @@ export function onStreamTimeout() {
|
||||
})
|
||||
}
|
||||
|
||||
/** 启动/重置看门狗(活跃事件或 sendMessage 时调用) */
|
||||
export function resetStreamWatchdog() {
|
||||
if (_streamWatchdog) clearTimeout(_streamWatchdog)
|
||||
_streamWatchdog = setTimeout(onStreamTimeout, STREAM_TIMEOUT_MS)
|
||||
/**
|
||||
* 启动/重置看门狗(活跃事件或 sendMessage 时调用)。
|
||||
*
|
||||
* TD-260621-01 per-conv:传 convId → 走 per-conv Map(arm 前 clear 该 conv 旧 timer,防重入堆积);
|
||||
* 省略 convId(禁动域 useAiSend/useAiApproval 调用)→ 走 _legacyWatchdog fallback(单 timer,行为对齐旧版)。
|
||||
* setTimeout 回调闭包捕获 convId,到期 onStreamTimeout(convId) 精确清该 conv 态。
|
||||
*/
|
||||
export function resetStreamWatchdog(convId?: string) {
|
||||
if (convId) {
|
||||
const old = _watchdogTimers.get(convId)
|
||||
if (old) clearTimeout(old)
|
||||
const timer = setTimeout(() => onStreamTimeout(convId), STREAM_TIMEOUT_MS)
|
||||
_watchdogTimers.set(convId, timer)
|
||||
} else {
|
||||
// 禁动域 fallback:行为对齐旧版单 timer。
|
||||
if (_legacyWatchdog) clearTimeout(_legacyWatchdog)
|
||||
_legacyWatchdog = setTimeout(() => onStreamTimeout(), STREAM_TIMEOUT_MS)
|
||||
}
|
||||
}
|
||||
|
||||
/** 清除看门狗(完成/错误/审批等待时调用) */
|
||||
export function clearStreamWatchdog() {
|
||||
if (_streamWatchdog) { clearTimeout(_streamWatchdog); _streamWatchdog = null }
|
||||
/**
|
||||
* 清除看门狗(完成/错误/审批等待时调用)。
|
||||
*
|
||||
* TD-260621-01 per-conv:传 convId → 仅清该 conv 的 timer;省略 → 清 _legacyWatchdog fallback。
|
||||
* 注:省略 convId 时**不**清 Map(避免禁动域全局 clear 误清并发会话的 per-conv timer)。
|
||||
* stopListener 卸载场景需清全部,调 clearAllStreamWatchdogs()。
|
||||
*/
|
||||
export function clearStreamWatchdog(convId?: string) {
|
||||
if (convId) {
|
||||
const timer = _watchdogTimers.get(convId)
|
||||
if (timer) { clearTimeout(timer); _watchdogTimers.delete(convId) }
|
||||
} else {
|
||||
if (_legacyWatchdog) { clearTimeout(_legacyWatchdog); _legacyWatchdog = null }
|
||||
}
|
||||
}
|
||||
|
||||
/** 清除全部看门狗 timer(per-conv Map + legacy fallback)—— stopListener 卸载场景调用,
|
||||
* 防回调写已卸载组件的 state。 */
|
||||
export function clearAllStreamWatchdogs() {
|
||||
for (const timer of _watchdogTimers.values()) clearTimeout(timer)
|
||||
_watchdogTimers.clear()
|
||||
if (_legacyWatchdog) { clearTimeout(_legacyWatchdog); _legacyWatchdog = null }
|
||||
}
|
||||
|
||||
export function useAiStream() {
|
||||
@@ -86,5 +144,6 @@ export function useAiStream() {
|
||||
onStreamTimeout,
|
||||
resetStreamWatchdog,
|
||||
clearStreamWatchdog,
|
||||
clearAllStreamWatchdogs,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user