新增: 无人值守P0任务边界全链

- 任务级授权边界判定与审批旁路单点插桩
- boundary_json 随会话落库(V45 可逆迁移)
- 六路非正常终局统一安全停靠与托盘通知
- 每五轮进度事件与会话头部进度条
- 前端边界配置卡侧栏徽标及 UX 校验补齐
This commit is contained in:
lxy
2026-10-04 16:27:30 +08:00
parent 8396849d04
commit b2d44f85e9
29 changed files with 2773 additions and 142 deletions
+42 -5
View File
@@ -1,6 +1,6 @@
//! 生命周期事件域 — handleLifecycleEvent + cleanupTerminatedConversation
//!
//! 处理:AiCompleted / AiError / AiHelpRequired + 上下文生命周期事件(AiCompressing/
//! 处理:AiCompleted / AiError / AiHelpRequired / AiTaskParked + 上下文生命周期事件(AiCompressing/
//! AiManualCompressed/AiAutoCompressed/AiContextCleared)。
//! 依赖方向:本模块 → aiShared / useAiPendingState(叶子)/ useAiStream / streamingGuard /
//! useAiConversations,不导入任何同级子 composable。
@@ -10,7 +10,7 @@ import { useAppSettingsStore } from '@/stores/appSettings'
import { state } from '@/stores/ai'
import { t } from '@/i18n/i18n-helpers'
import { nextMsgId, convStates, clearApprovalTimer, flushCurrentText, clearAllToolSlowTimers, friendlyError, notifyConversationChanged, clearLastDelta, discardConvPendingApprovals, notifyQueueCleared } from './aiShared'
import { clearTextIdleTimer, pendingMaxRounds, pendingHelp, pendingDirAuths } from './useAiPendingState'
import { clearTextIdleTimer, pendingMaxRounds, pendingHelp, pendingDirAuths, pendingTaskParked, pendingTaskProgress } from './useAiPendingState'
import { clearStreamWatchdog, clearResumeAttempts } from './useAiStream'
import { setStreaming } from './streamingGuard'
import type { AiChatEvent, AiMessage, MessageId } from '@/api/types'
@@ -43,14 +43,16 @@ function isShowTokenUsage(): boolean {
}
/**
* 公共会话终止收尾(AiCompleted/AiError/AiHelpRequired 共用):清看门狗/计时器/流式态/挂起。
* 公共会话终止收尾(AiCompleted/AiError/AiHelpRequired/AiTaskParked 共用):清看门狗/计时器/流式态/挂起。
*
* 语义差异(调用方自行处理):
* - AiCompleted: 调用后追加 incomplete 气泡 / token 用量 / 队列 drain
* - AiError: 调用后追加错误气泡 + 清 queue
* - AiHelpRequired: 调用后翻 pendingHelp 驱动求助卡
* - AiTaskParked: 调用后登记 pendingTaskParked 驱动停靠卡(摘旧卡;不清 pendingHelp,
* 熔断并存场景见下方 pendingHelp 分支注释)
*/
function cleanupTerminatedConversation(convId: string, reason: 'AiCompleted' | 'AiError' | 'AiHelpRequired') {
function cleanupTerminatedConversation(convId: string, reason: 'AiCompleted' | 'AiError' | 'AiHelpRequired' | 'AiTaskParked') {
clearStreamWatchdog(convId || undefined)
// 正常收尾路径清该会话的 delta 去重记录:flushCurrentText 只清活跃会话的,这里按 convId
// 显式清一遍兜住 convId 与 active 不一致或事件缺 convId 的时序,防残留记录误丢下轮同内容 delta
@@ -83,8 +85,16 @@ function cleanupTerminatedConversation(convId: string, reason: 'AiCompleted' | '
pendingMaxRounds.value = null
}
if (pendingHelp.value && pendingHelp.value.conversationId === convId) {
pendingHelp.value = null
// 熔断场景后端并存发求助卡+停靠卡(agentic/mod.rs 熔断分支先 emit 求助卡再「补发」
// AiTaskParked,注释自称并存意图):停靠收尾不清求助卡,help options 三选项不丢,
// 两卡并存供用户任选入口。其余终态(AiCompleted/AiError/AiHelpRequired)照旧清。
if (reason !== 'AiTaskParked') pendingHelp.value = null
}
// 无人值守停靠卡:会话有任何终态(含停靠本身)即摘旧卡;停靠 case 在收尾后重新登记,
// 其余终态(AiCompleted/AiError,点继续续跑后的正常收敛/出错)摘卡即消失,语义对齐 pendingHelp
pendingTaskParked.delete(convId)
// 无人值守进度快照:终态即清(事件即逝态,终止后不留陈旧「进行中」;下次 loop 每 5 轮重建)
pendingTaskProgress.delete(convId)
// 路径授权挂起仅清本 conv(终止只清本会话弹窗,不连累并发会话;无归属旧项保守保留)
pendingDirAuths.value = pendingDirAuths.value.filter(p => !p.conversationId || p.conversationId !== convId)
// 审批收尾仅清目标 conv 的待审批项 + 对称清其超时计时器(其他会话的审批卡/计时器不受影响;
@@ -104,6 +114,21 @@ function cleanupTerminatedConversation(convId: string, reason: 'AiCompleted' | '
localStorage.removeItem('df-ai-text')
}
/**
* 登记无人值守停靠卡(幂等,convId 键控覆盖)。lifecycle 主路径与 useAiEvents 后台会话
* 分支共用唯一写入口,防止两处字段口径漂移。convId 缺失兜底当前活跃会话(优于丢卡)。
*/
export function recordTaskParked(convId: string | null | undefined, reason: string, stopKind?: string): void {
const id = convId || state.activeConversationId
if (!id) return
pendingTaskParked.set(id, {
reason,
stopKind: stopKind || undefined,
conversationId: id,
parkedAt: Date.now(),
})
}
/** lifecycle 域:整轮收尾(AiCompleted)与异常中断(AiError)。返回 true=已处理。 */
export function handleLifecycleEvent(event: AiChatEvent): boolean {
switch (event.type) {
@@ -215,6 +240,18 @@ export function handleLifecycleEvent(event: AiChatEvent): boolean {
return true
}
case 'AiTaskParked': {
// 无人值守安全停靠(设计 §四):五路非正常终局统一出口,后端已保存现场并复位
// generating(guard 复位非死等)。对齐 AiHelpRequired 家族:终止态收尾不建错误气泡,
// 改登记 pendingTaskParked 驱动停靠卡(reason + stop_kind 标签 + 继续/停止归档)。
// 队列不动:停靠等用户拍板,排队消息不自动续发不静默清(继续/停止由用户决定)。
cleanupTerminatedConversation(event.conversation_id || '', 'AiTaskParked')
recordTaskParked(event.conversation_id, event.reason, event.stop_kind ?? undefined)
// FE1:停靠刷新侧栏(更新活跃时间排序),由 listener 防抖调度统一收口。
notifyConversationChanged()
return true
}
case 'AiCompressing':
_contextCallbacks.onCompressing?.()
return true