diff --git a/src-tauri/src/commands/workflow.rs b/src-tauri/src/commands/workflow.rs index 9af3cec..e648492 100644 --- a/src-tauri/src/commands/workflow.rs +++ b/src-tauri/src/commands/workflow.rs @@ -185,6 +185,10 @@ pub async fn approve_human_approval( decision: String, comment: Option, ) -> Result<(), String> { + // decision 非空校验(FR-S3: 防 "" 透传;HumanNode options 非空时还校验 ∈ options) + if decision.trim().is_empty() { + return Err("审批决策不能为空".to_string()); + } // 发送审批响应到事件总线 let response = HumanApprovalResponse { execution_id: execution_id.clone(), diff --git a/src/composables/ai/useAiConversations.ts b/src/composables/ai/useAiConversations.ts index 65013c1..4f03cbd 100644 --- a/src/composables/ai/useAiConversations.ts +++ b/src/composables/ai/useAiConversations.ts @@ -40,10 +40,16 @@ async function newConversation() { notifyConversationChanged() } +// 切换 token:快速连点 A→B 时,后返回的 A 响应按 token 丢弃,防 messages 错配(FR-R1) +let _latestSwitchId = 0 + /** 切换到指定会话:加载历史消息(含 tool_calls 回填 + tool_result 映射 + pending 审批恢复) */ export async function switchConversation(id: string) { + const mySwitchId = ++_latestSwitchId // 允许生成中切换:后台继续生成,事件按 conversation_id 路由不污染当前视图 const detail = await aiApi.switchConversation(id) + // 过期响应丢弃(用户已切到别的对话,防 A 后返回覆盖 B) + if (mySwitchId !== _latestSwitchId) return state.activeConversationId = id void appSettings.set('df-ai-active-conv', id)