新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(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:
@@ -24,27 +24,56 @@ import { findToolCall } from './aiShared'
|
||||
*/
|
||||
const _pendingApprovalIds = new Set<string>()
|
||||
|
||||
/** 工具审批:保持 pending_approval 让审批卡片可见(按钮 loading 由 ToolCard 本地 ref 持有),
|
||||
/**
|
||||
* 工具审批:保持 pending_approval 让审批卡片可见(按钮 loading 由 ToolCard 本地 ref 持有),
|
||||
* IPC 失败时改 completed+错误文案。后端回事件后由 useAiEvents 转 completed/rejected。
|
||||
* B-260616-08:不再乐观置 running——原写法让 .ai-tool-approval 整块消失(切骨架屏),
|
||||
* 按钮无 loading 中间态、用户无重审入口;loading 现下沉到 ToolCard 局部 ref,语义正确。 */
|
||||
async function approveToolCall(toolCallId: string, approved: boolean) {
|
||||
* 按钮无 loading 中间态、用户无重审入口;loading 现下沉到 ToolCard 局部 ref,语义正确。
|
||||
*
|
||||
* path_auth 审批链阶段3b(统一审批模型):**按 tc.kind 分派**两条 IPC——
|
||||
* - kind='path'(路径授权挂起):调 aiApi.authorizeDir(toolCallId, decision) once/always/deny。
|
||||
* once=仅本次会话授权 / always=写持久白名单 / deny=拒绝。
|
||||
* - kind='risk'(普通 RiskLevel 审批,默认):调 aiApi.approve(toolCallId, approved) 一次性 approve/reject。
|
||||
* approved=true→放行执行,approved=false→拒绝。
|
||||
* 入口据 findToolCall(toolCallId).kind 判定,kind 缺省走 risk 老路径(兼容非统一开关 / 老 toolCall)。
|
||||
* **注意**:决策分派由 tc.kind 决定,与调用方是否传 decision 入参无关——decision 仅 path 类消费,
|
||||
* 调用方传 decision 但 tc.kind='risk' 时仍走 approve(approved)分支(忽略 decision)。
|
||||
*
|
||||
* @param approved risk 类专用(approve/reject, approve=true/reject=false);path 类忽略。
|
||||
* @param decision path 类专用('once'|'always'|'deny');risk 类忽略。调用方须据 tc.kind 传对应入参。
|
||||
*/
|
||||
async function approveToolCall(toolCallId: string, approved: boolean, decision?: 'once' | 'always' | 'deny') {
|
||||
// F-260616-06: 防抖——同一 id 仍在处理中则跳过(竞态点击/网络慢重试)
|
||||
if (_pendingApprovalIds.has(toolCallId)) return
|
||||
_pendingApprovalIds.add(toolCallId)
|
||||
try {
|
||||
// 复用 findToolCall(反向扫描)避免重复实现查找逻辑;仅缓存引用用于 IPC 失败回滚
|
||||
const tc = findToolCall(toolCallId)
|
||||
// 重启看门狗覆盖审批执行→续生成窗口(useAiEvents.ts:162 AiApprovalRequired 已 clear,
|
||||
// path_auth 审批链阶段3b:按 tc.kind 分派 IPC。kind='path' 走 authorizeDir(once/always/deny);
|
||||
// 其余(risk/缺省)走 approve(approve/reject)。
|
||||
const isPathKind = !!tc && tc.kind === 'path'
|
||||
// 重启看门狗覆盖审批执行→续生成窗口(useAiEvents.ts AiApprovalRequired/AiDirAuthRequired 已 clear,
|
||||
// 审批态无心跳兜底;approve 后后端要跑工具+续生成,期间任何后端异常不回则按钮永久 loading。
|
||||
// 复用通用 watchdog 而非加审批专用超时:复用同一收尾路径(onStreamTimeout 兜底复位 streaming),
|
||||
// 避免新增独立计时器与状态分支)
|
||||
resetStreamWatchdog()
|
||||
// TD-260621-03 per-conv:看门狗 timer 用**审批工具调用所属 conv**——tc 来自 findToolCall 反扫
|
||||
// state.messages(当前活跃会话的单例视图),故 tc 所属 conv 即当前 activeConversationId。
|
||||
// 切会话时 switchConversation 已整体替换 state.messages+pendingApprovals(单例,非 per-conv Map),
|
||||
// 旧 conv 的 tc 不再可达,故 activeConversationId 是 tc 所属 conv 的正确等价表达。
|
||||
// 复用此 convId 供 reset 与 catch 内 clear 对称使用(同会话 pair,不跨会话误清)。
|
||||
const approvalConvId = state.activeConversationId || undefined
|
||||
resetStreamWatchdog(approvalConvId)
|
||||
try {
|
||||
await aiApi.approve(toolCallId, approved)
|
||||
if (isPathKind) {
|
||||
// path 类:decision 缺省兜底 deny(调用方未传时安全侧拒绝,不静默放行)。
|
||||
await aiApi.authorizeDir(toolCallId, decision ?? 'deny')
|
||||
} else {
|
||||
await aiApi.approve(toolCallId, approved)
|
||||
}
|
||||
} catch (e) {
|
||||
// IPC 未送达:用户已点过按钮,清看门狗(避免 130s 后误触发假错误)再回滚到失败态
|
||||
clearStreamWatchdog()
|
||||
// TD-260621-03 per-conv:清审批 tc 所属会话的 timer(与上方 reset 同 conv,对称成对)。
|
||||
clearStreamWatchdog(approvalConvId)
|
||||
state.queue = [] // B-32:IPC 失败即对话结束,清队列防生成中入队消息静默丢失
|
||||
// 仅 IPC 真失败(审批已处理/网络断)走到这里:后端工具失败已改走 emit completed,不进此分支
|
||||
// 不回滚 pending_approval(会卡死按钮),改设 completed + 错误提示,让用户知晓失败
|
||||
@@ -60,14 +89,29 @@ async function approveToolCall(toolCallId: string, approved: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 批量审批:遍历 pendingApprovals 逐个调用 approveToolCall */
|
||||
/**
|
||||
* 批量审批:遍历 pendingApprovals 逐个按 kind 分派调用 approveToolCall(决策:批量逐个,
|
||||
* 非目录聚合——path 类每条独立决策,不合并同目录)。
|
||||
*
|
||||
* path_auth 审批链阶段3b:统一开关开后,pendingApprovals 可能混合 path + risk 两类挂起。
|
||||
* 批量操作语义:
|
||||
* - decision='approve':risk 类 approved=true;path 类 decision='once'(批量授权仅本次,
|
||||
* 不批量写持久白名单——always 是单卡语义,批量误写持久白名单风险高,故批量只 once)。
|
||||
* - decision='reject':risk 类 approved=false;path 类 decision='deny'。
|
||||
* 单条失败不中断批量(已由 approveToolCall 内部回滚该条状态)。
|
||||
*/
|
||||
async function batchApprove(decision: 'approve' | 'reject') {
|
||||
const approved = decision === 'approve'
|
||||
// 快照当前待审批列表(遍历中 state.pendingApprovals 会因事件回调而缩短)
|
||||
const ids = [...state.pendingApprovals].map(p => p.id)
|
||||
for (const id of ids) {
|
||||
const items = [...state.pendingApprovals]
|
||||
for (const p of items) {
|
||||
try {
|
||||
await approveToolCall(id, approved)
|
||||
if (p.kind === 'path') {
|
||||
// path 类:approve→once(仅本次,不批量写持久白名单), reject→deny
|
||||
await approveToolCall(p.id, false, decision === 'approve' ? 'once' : 'deny')
|
||||
} else {
|
||||
// risk 类:approve/reject boolean
|
||||
await approveToolCall(p.id, decision === 'approve')
|
||||
}
|
||||
} catch {
|
||||
// 单条失败不中断批量操作(已由 approveToolCall 内部回滚该条状态)
|
||||
// 继续处理剩余项
|
||||
|
||||
Reference in New Issue
Block a user