修复: @任务联想源隔离 store.tasks 污染 + DirAuthDialog 拒绝二次确认 + 看板销账(AC弱模型机制 3/5 已落地核对)

This commit is contained in:
lxy
2026-08-09 21:34:52 +08:00
parent 180d8e339f
commit cad76ffdf2
3 changed files with 48 additions and 12 deletions
+10 -10
View File
@@ -385,11 +385,11 @@ graph TD
- [x]**P1-a** 长内容折叠(MessageList AI 文本气泡>1500 字 + ConflictResolver 多冲突 + HelpRequiredCard 堆栈,`9e85cad`)
- [x]**P1-b** Dashboard 统计源解耦(治 H6/H7 翻页/筛选后统计错乱;统计走后端 count_tasks 独立通道,`d1321af`)
- [ ] **P1-c** 列表状态持久化(抽 `usePersistedRef` composable + Tasks/Ideas/Knowledge/Projects/ProjectDetail Tab 接入)
- [ ] **P1-d** AuditLog 后端 WHERE 筛选 + 真实 total + 前端持久化(治"客户端筛选只作用当前页 50 条"假象)
- [ ] **P1-e** 审批体系一致(ApprovalPopup 浮窗挂起计时 H1 + 授权粒度统一 ToolCard/Popup/DirAuthDialog + 拒绝二次确认 + 失败 toast)
- [ ] **P1-f** cmd 闪烁 N²(FileTree 展开缓存 git status,避免每次全仓扫描)
- [ ] **P1-g** 延伸:ProjectDetail.vue:368 + ChatInput.vue:367 同源 `store.tasks.filter` 污染(项目任务展示 / @ 联想,P1-b 核查时发现)
- [x] **P1-c** 列表状态持久化**2026-08-09 核验销账**:usePersistedRef 已接入 Tasks/Projects/Knowledge/Ideas/AuditLog;ProjectDetail Tab 手写 localStorage 按 projectId 隔离(动态 key,usePersistedRef 静态 key 不适用)保留
- [x] **P1-d** AuditLog 后端 WHERE 筛选 + 真实 total **2026-08-09 核验销账**:list_tool_executions(record.rs:182)支持 status/risk_level/tool_keyword WHERE + count_by_query 独立 COUNT 真实 total + limit/offset 分页;前端 AuditLog.vue buildQuery 传后端 + usePersistedRef 持久化
- [~] **P1-e** 审批体系一致**部分(2026-08-09 核验)**:挂起计时✅(ApprovalPopup :191 MM:SS)/ 授权粒度统一✅(ToolCard/ApprovalPopup/DirAuthDialog 同 i18n)/ deny 二次确认✅(本批补 DirAuthDialog)/ **失败 toast 🟡待设计**(ApprovalPopup runApproval 乐观移除且 catch 仅 console.error;useAiApproval 失败仅气泡无 toast,需设计 toast 接入)
- [~] **P1-f** cmd 闪烁 N²**部分(2026-08-09 核验)**:前端展开缓存已做(FileExplorer loadedChildren 去重);**后端每次展开新目录仍整仓 git status 扫描(module.rs:654)待架构级缓存,登记待设计**
- [x] **P1-g** store.tasks.filter 污染 — **2026-08-09 核验+补全销账**:ProjectDetail 独立 taskApi.list 已修;ChatInput @项目 enrichment 独立拉取(projectTasksCache)已修 + @任务联想本批改独立全量缓存 taskSuggestionCache(隔离 Tasks 页筛选子集)+ mentionTotal 对齐
**P2(打磨)**:幽灵 `--df-primary` / git 状态色 token / 控件统一(原问题 8 延伸)/ 死代码清理(PlanProgress/isLastUser)
@@ -403,11 +403,11 @@ graph TD
> 实证:prod(76 会话/9928 消息/6392 工具)+ dev(98 会话/7853 消息)库 + 会话 dump 反推。详单 [aichat历史会话实证诊断-2026-08-04.md](./05-代码审查/aichat历史会话实证诊断-2026-08-04.md)。
> 核心:弱模型(sensenova-6.7-flash-lite)在任务中**重复探索/绕工具/参数错误**是系统性现象,非偶发。
- [ ] **AC-1** 机制层「同参数重复调用」拦截:同会话同工具同参数组 ≥2 次即返缓存/警告(现仅 prompt.rs:107 说教,弱模型无效;**实证 83 个重复组合 ≥3 次**,09e7abfa 同一文件读 6 次、e0b5d8fc update_task 同参 8 次)
- [ ] **AC-2** 探索预算上限:单轮工具调用数上限 + 目录列举去重 + list_directory 后禁止绕 run_command 同探(09e7abfa 同一目录列 3 次再 Get-ChildItem 绕行)
- [ ] **AC-3** 系统 prompt 项目清单补 UUID(prompt.rs:304 注入 `- {name}: {desc}` 无 id):**实证 list_tasks("DevFlow"/"meta-kit"/"u-ask") 12+ 处传 name 当 id 全返空**
- [ ] **AC-4** read_symbol 采用率诊断:**实证 read_file 2582 vs read_symbol 36(采用率 1.4%)**,AST 代码智能(7c2e3b2)落地空转,prompt 引导对弱模型无效,需机制(read_file 命中已知符号时提示换 read_symbol / 按模型分级)
- [ ] **AC-5** 失败工具画像复盘:run_command 67 / read_file 55 / search_files 33 / advance_task 31(状态机拒) / patch_file 27,逐类找机制根因降失败
- [x] **AC-1** ✅ 已落地(2026-08-09 核验):双层拦截——调用级 `cache.rs:251 find_cached_readonly_result`(同 tool_name+canonical_args 已 completed 即返缓存+警告头,第 2 次即命中)+ 轮级 `is_repetitive_exploration`(helpers.rs:304-332,单签名≥3/唯一率<40%)→ `check_stall_breaker`(警示→硬熔断 emit AiHelpRequired)
- [x] **AC-2** ✅ 已落地(2026-08-09 核验,提交 fe780c0):`MAX_TOOL_CALLS_PER_ROUND=8`(agentic/mod.rs:245)+ 目录列举去重(readonly_cache_args_key 只取 path,返目录专用警告)+ `detect_listing_bypass`(run_command 含列举命令且引用已列目录→警告,保守式不硬拒)
- [x] **AC-3** ✅ 已落地(2026-08-09 核验):prompt.rs:304/323 项目/任务清单已含 id + 注明语(projects_listed_note "引用用 id 非名称");**兜底机制超要求**:entity_resolve.rs name→id 自动解析(RESOLVE_MAP 12 工具 + UUID 跳过 + 0 命中/重名返可行动错误 + 9 单测,audit/mod.rs process_tool_calls 单点漏斗)
- [~] **AC-4** 🟡 部分(2026-08-09 核验):read_file 结果级提示已落地(tools/file.rs:185-197,支持语言集内追加"改用 read_symbol")+ 三态本体(code_intel.rs:130)+ 缓存兜底;**缺口**:无按模型分级、无符号级命中检测、无采用率回测(prompt 引导对弱模型仍偏弱,待评估)
- [~] **AC-5** 🟡 部分(2026-08-09 核验):画像复盘已做(docs/AI工具失败画像复盘-2026-08-08.md)+ 2/5 自愈机制落地(advance_task legal_targets 回灌 task_state_machine.rs:121 / patch_file 相近锚点 file.rs:552);**缺口**:无运行时按工具失败率统计机制(read_file 相近文件候选、search_files 空结果引导为复盘建议未落地,待评估)
### 💡 2026-08-04 MCP 多进程架构潜在问题(分析登记)
+17 -2
View File
@@ -346,6 +346,8 @@ function detectMentionTrigger() {
mentionStart.value = i
mentionOpen.value = true
mentionIndex.value = 0
// P1-g: 用户首次进入 @ 联想即懒加载全量任务候选(幂等,缓存判重;不在 computed 副作用内调用)
void ensureTaskSuggestions()
return
}
// 形如 a@b:前字符非空白 → 不是 mention 触发符,继续向前扫也只会找到更早的 @,
@@ -403,6 +405,19 @@ const pendingMentionSpans = ref<MentionSpan[]>([])
const MENTION_TASK_LIMIT = 50
const projectTasksCache = ref<Map<string, TaskRecord[]>>(new Map())
// P1-g: @任务联想候选源隔离 store.tasks(反映 Tasks 视图筛选+分页子集)。
// 懒加载全量未删任务(taskApi.list() 无参=全量)+ 缓存;首次 @ 触发拉取,失败空数组占位防反复重试。
const taskSuggestionCache = ref<TaskRecord[] | null>(null)
async function ensureTaskSuggestions(): Promise<void> {
if (taskSuggestionCache.value) return
try {
taskSuggestionCache.value = await taskApi.list()
} catch (e) {
taskSuggestionCache.value = []
console.error('[ChatInput] 加载任务联想失败:', e)
}
}
/** 拉取指定项目的任务并缓存(已缓存则跳过);失败静默降级为空数组(不阻塞 enrichment 渲染) */
async function ensureProjectTasks(projectId: string): Promise<void> {
if (projectTasksCache.value.has(projectId)) return
@@ -564,7 +579,7 @@ const mentionQuery = computed(() => {
const mentionItems = computed<MentionItem[]>(() => {
const q = mentionQuery.value.trim().toLowerCase()
const projects = (projectsStore.projects || []) as ProjectRecord[]
const tasks = (projectsStore.tasks || []) as TaskRecord[]
const tasks = (taskSuggestionCache.value ?? []) as TaskRecord[]
const match = (s: string) => !q || s.toLowerCase().includes(q)
const projectItems: MentionItem[] = projects
.filter(p => match(p.name) || match(p.description || ''))
@@ -606,7 +621,7 @@ const mentionItems = computed<MentionItem[]>(() => {
/** 联想候选总数(未过滤,用于区分"无匹配" vs "空") */
const mentionTotal = computed(() =>
((projectsStore.projects || []) as ProjectRecord[]).length
+ ((projectsStore.tasks || []) as TaskRecord[]).length
+ (taskSuggestionCache.value?.length ?? 0)
+ ((projectsStore.ideas || []) as IdeaRecord[]).length,
)
+21
View File
@@ -37,6 +37,13 @@
>{{ $t('aiChat.dirAuthDeny') }}</button>
</div>
</div>
<!-- P1-e-c: deny 拒绝二次确认(拒绝不可逆工具返 Errloop 续跑不再重试;对齐 ToolCard/ApprovalPopup) -->
<ConfirmDialog
:visible="confirmState.visible"
:msg="confirmState.msg"
:danger-label="$t('common.confirm')"
@result="answerConfirm"
/>
</template>
<script setup lang="ts">
@@ -45,6 +52,8 @@ import { useI18n } from 'vue-i18n'
import { useAiStore } from '../../stores/ai'
import { pendingDirAuths } from '../../composables/ai/useAiPendingState'
import { aiApi } from '../../api'
import { useConfirm } from '../../composables/useConfirm'
import ConfirmDialog from '../../components/ConfirmDialog.vue'
const emit = defineEmits<{
(e: 'toast', payload: { msg: string; type: 'error' | 'warning' | 'info' }): void
@@ -53,6 +62,9 @@ const emit = defineEmits<{
const store = useAiStore()
const { t } = useI18n()
// P1-e-c: deny 拒绝二次确认统一弹层(useConfirm 状态机 + ConfirmDialog 组件,同 ToolCard/ApprovalPopup)
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
// F-260620 根治(授权挂起无声卡死):挂起态弹窗显示只依赖 isGenerating(conv),不依赖 streaming。
// streaming 会被流式看门狗超时清(useAiStream onStreamTimeout),而 AiDirAuthRequired 挂起常发生在
// 首轮慢响应(看门狗已超时清 streaming)之后——此时 streaming=false 但后端 generating=true、
@@ -87,6 +99,15 @@ const dirAuthRefs = ref<HTMLElement[]>([])
async function handleAuthorize(id: string, decision: 'once' | 'session' | 'always' | 'deny'): Promise<void> {
if (actingIds.has(id)) return
actingIds.add(id)
// P1-e-c: deny 拒绝前二次确认(拒绝不可逆——工具返 Err、loop 续跑不再重试;与 ToolCard onDeny 同款)。
// 放 actingIds.add 之后、IPC 之前;取消则删 actingIds 复位处理中态(保持 pending 可重新决策),不调用 IPC。
if (decision === 'deny') {
const ok = await confirmDialog(t('aiChat.dirAuthDenyConfirm'))
if (!ok) {
actingIds.delete(id)
return
}
}
try {
await aiApi.authorizeDir(id, decision)
} catch (e) {