新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理

后端:
- 工作流推进链(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:
lxy
2026-06-21 20:51:26 +08:00
parent 330bb7f505
commit bd6a41fe6e
111 changed files with 11931 additions and 1033 deletions
+54 -32
View File
@@ -1,41 +1,43 @@
<template>
<!-- F-260619-03 Phase B: 路径授权弹窗(LLM 想访问白名单外目录,后端挂起 loop 等用户决定)。
条件:pendingDirAuth(AiDirAuthRequired 事件置)+ 当前视图正在生成(防切走后误显)。
点"仅本次" → ai_authorize_dir(decision=once, 写会话临时授权)→ 后端执行工具 + try_continue。
<!-- F-260619-03 Phase B + path_auth 审批链阶段1: 路径授权弹窗(LLM 想访问白名单外目录,
后端挂起 loop 等用户决定)。阶段1 改遍历 pendingDirAuths(数组)——同轮多个文件工具落不同
未授权目录时后端连发多条 AiDirAuthRequired,数组并存不互覆盖。
条件:pendingDirAuths 非空(AiDirAuthRequired 事件置)+ 当前视图正在生成(防切走后误显)。
每项独立三按钮:点"仅本次" → ai_authorize_dir(decision=once, 写会话临时授权)→ 后端执行工具 + try_continue。
点"未来都允许" → ai_authorize_dir(decision=always, 写持久化 Settings KV)→ 执行 + 续 loop。
点"拒绝" → ai_authorize_dir(decision=deny, 工具返 Err)→ 续 loop。
store 单例共享(state/aiApi),pendingDirAuth 模块级 ref(useAiEvents)直接导入。
store 单例共享(state/aiApi),pendingDirAuths 模块级 ref(useAiEvents)直接导入。
toast 经 emit 转父(保持单一 toast 源)。 -->
<div v-if="showDirAuthCard" class="ai-dir-auth">
<div v-for="item in visibleDirAuths" :key="item.id" class="ai-dir-auth">
<span class="ai-dir-auth-text">{{ $t('aiChat.dirAuthRequired') }}</span>
<span class="ai-dir-auth-hint">
{{ $t('aiChat.dirAuthHint', { tool: pendingDirAuth?.tool, path: pendingDirAuth?.path }) }}
{{ $t('aiChat.dirAuthHint', { tool: item.tool, path: item.path }) }}
</span>
<div class="ai-dir-auth-actions">
<button
class="ai-dir-auth-btn ai-dir-auth-btn--once"
:disabled="dirAuthActing"
@click="handleAuthorize('once')"
:disabled="actingIds.has(item.id)"
@click="handleAuthorize(item.id, 'once')"
>{{ $t('aiChat.dirAuthOnce') }}</button>
<button
class="ai-dir-auth-btn ai-dir-auth-btn--always"
:disabled="dirAuthActing"
@click="handleAuthorize('always')"
:disabled="actingIds.has(item.id)"
@click="handleAuthorize(item.id, 'always')"
>{{ $t('aiChat.dirAuthAlways') }}</button>
<button
class="ai-dir-auth-btn ai-dir-auth-btn--deny"
:disabled="dirAuthActing"
@click="handleAuthorize('deny')"
:disabled="actingIds.has(item.id)"
@click="handleAuthorize(item.id, 'deny')"
>{{ $t('aiChat.dirAuthDeny') }}</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { reactive, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAiStore } from '../../stores/ai'
import { pendingDirAuth } from '../../composables/ai/useAiEvents'
import { pendingDirAuths } from '../../composables/ai/useAiEvents'
import { aiApi } from '../../api'
const emit = defineEmits<{
@@ -45,37 +47,57 @@ const emit = defineEmits<{
const store = useAiStore()
const { t } = useI18n()
// 当前视图是否正在生成(切走后台生成时光标不显示,与 MaxRoundsCard 一致守卫)
// F-260620 根治(授权挂起无声卡死):挂起态弹窗显示只依赖 isGenerating(conv),不依赖 streaming。
// streaming 会被流式看门狗超时清(useAiStream onStreamTimeout),而 AiDirAuthRequired 挂起常发生在
// 首轮慢响应(看门狗已超时清 streaming)之后——此时 streaming=false 但后端 generating=true、
// generatingConvs 含 conv(useAiEvents handleEvent 对非完成/错误事件 add)。若守卫仍 && streaming,
// 挂起弹窗永不显示 → loop 无声卡死(用户发消息无响应)。去 streaming,isGenerating(conv) 单条件覆盖。
// F-09: 多会话并发 — isGenerating(active) 替代单值比对
const isViewingGenerating = computed(() =>
store.state.streaming && store.isGenerating(store.state.activeConversationId),
store.isGenerating(store.state.activeConversationId),
)
// pendingDirAuth(AiDirAuthRequired 事件置)+ 当前视图正在生成(防切走后误显)双重守卫。
const dirAuthActing = ref(false)
const showDirAuthCard = computed(() =>
pendingDirAuth.value !== null && isViewingGenerating.value,
)
// pendingDirAuths 非空 + 当前视图正在生成(防切走后误显)双重守卫。
// 阶段1:数组化后,visibleDirAuths 是 pendingDirAuths 的派生视图(按 conv 过滤当前会话挂起项),
// 与 showDirAuthCard 合并为单一 computed(非空即显)。
const visibleDirAuths = computed(() => {
if (!isViewingGenerating.value) return []
// 仅显当前会话的挂起项(active conv);其他会话的挂起不在本视图弹窗。
const active = store.state.activeConversationId
return pendingDirAuths.value.filter(p => !p.conversationId || p.conversationId === active)
})
/** 点三选项之一:调 ai_authorize_dir。后端 remove pending → 写授权/拒 → execute → try_continue。
* 不主动清 pendingDirAuth——由后端 AiApprovalResult/AiCompleted/AiError 在 useAiEvents 内清。
* IPC 失败回滚 acting 让用户可重试。 */
async function handleAuthorize(decision: 'once' | 'always' | 'deny'): Promise<void> {
const id = pendingDirAuth.value?.id
if (!id) return
dirAuthActing.value = true
// 阶段1:逐 id 追踪"处理中"态(数组多挂起并存,单 boolean 不够)。
// reactive Set 的增删驱动按钮 disabled;Set 判定 O(1)。
const actingIds = reactive(new Set<string>())
/** 点三选项之一:按 id 定位挂起项,调 ai_authorize_dir。后端 remove pending → 写授权/拒 →
* execute → try_continue。不主动清 pendingDirAuths——由后端 AiApprovalResult/AiCompleted/AiError
* 在 useAiEvents 内按 id filter / 清空。IPC 失败回滚 actingIds 让用户可重试。 */
async function handleAuthorize(id: string, decision: 'once' | 'always' | 'deny'): Promise<void> {
if (actingIds.has(id)) return
actingIds.add(id)
try {
await aiApi.authorizeDir(id, decision)
} catch (e) {
dirAuthActing.value = false
const msg = e instanceof Error ? e.message : String(e)
emit('toast', { msg: t('aiChat.dirAuthFailed', { msg }), type: 'error' })
actingIds.delete(id) // IPC 失败:复位该 id,允许重试
return
}
// F-260620 根治(授权按钮永转圈):IPC 成功后 try_continue spawn 续跑 loop,续跑 loop 可能立刻又
// 触发 AiDirAuthRequired(同 id 不可能,但其他 id 可能)置新 pendingDirAuths 项;该 id 的项会由
// AiApprovalResult 按 id filter 移除。这里不依赖 pendingDirAuths 状态复位 actingIds——改为
// watch pendingDirAuths,某 id 离开数组(已被后端消费)即删 actingIds,精准复位。
}
/** pendingDirAuth 离开挂起态(后端事件已清)时复位 acting,允许下次再操作 */
watch(() => pendingDirAuth.value, (v) => {
if (!v) dirAuthActing.value = false
/** pendingDirAuths 变化时,离开数组(已被后端 AiApprovalResult/AiCompleted/AiError 按 id 移除)的
* id 从 actingIds 删,精准复位"处理中"态(数组化后多挂起,单 watch===null 模式不再适用)。 */
watch(() => pendingDirAuths.value, (items) => {
const liveIds = new Set(items.map(p => p.id))
for (const id of [...actingIds]) {
if (!liveIds.has(id)) actingIds.delete(id)
}
})
</script>