修复: 飞书走查六项 + 停靠状态写侧根因解 + agentic/audit 加固
飞书集成走查六项(feishu_channel.rs +420):
1. 审批卡按钮化: on_card_action + handle_card_action(解析 {approval_id,approved}
→ ai_approve) + send_approval_button_card(Card JSON 1.0 双按钮, 失败降级文本卡)
2. 首帧流式提速: AiTextDelta 首个 delta 立即刷占位卡, 不等 3s ticker
3. 断线重连通知: 按 run_loop 时长判据(≥30s 推断开 / <30s 累计 3 次推不稳定),
新增 notify_monitor()
4. 占位卡带任务原文回显(truncate 60)
5. 白名单配置引导: 后端分步自助引导 + FeishuPanel hintFeishuWhitelist + i18n
6. 审批决议后追加执行结果: spawn_approval_result_watcher 订阅 AiToolCallCompleted
(匹配 tool_call_id, 60s 超时兜底); 订阅须在 ai_approve 前完成(无订阅者跳过序列化)
停靠(parked)写侧根因解(904):
emit_task_parked(ai/mod.rs) 升级为「写 parked + 发事件 + 通知」三件套——
新增 set_loop_state(conv_id, Some("parked")) fire-and-forget; 8 个业务调用点
(H2 三闸/熔断两路/挂起取消/max_rounds/midstream/fatal + 审批超时) 全经单点。
治「停靠卡继续必报 AI 未在暂停态」(全仓原无写 parked 路径)。
agentic/audit 加固: context_lifecycle/stream_relay/mod 调整 + audit record 完善 +
tool_failure_memory/tool_result_offload/tools/task/settings 同步。
验收: cargo check -p devflow EXIT 0。
This commit is contained in:
@@ -341,6 +341,19 @@ pub const CIRCUIT_BREAKER_ENABLED: bool = true;
|
||||
/// 本开关无意义;CIRCUIT_BREAKER_ENABLED=true 时本开关决定呈现形态。
|
||||
pub const CIRCUIT_BREAKER_HELP_EVENT: bool = true;
|
||||
|
||||
/// 熔断呈现形态开关(#904 附项,2026-10-09):熔断同轮双卡叠加治理。
|
||||
///
|
||||
/// 背景:熔断路径原同时 emit 两张卡——求助卡 AiHelpRequired(换思路/授权路径/人工接管)
|
||||
/// + 停靠卡 AiTaskParked(继续/归档),同轮叠加语义打架且文案冗长;且停靠卡「继续」
|
||||
/// 正是熔断后的正典恢复入口(ai_continue_loop → recover_parked_inner)。
|
||||
///
|
||||
/// true(默认):熔断只发停靠一张卡——AiTaskParked 为唯一呈现,reason 内联原求助卡的
|
||||
/// 关键信息(失败样本/路径),前端停靠卡即含恢复指引,零双卡叠加。
|
||||
/// false(回退):保留旧行为(求助卡 + 停靠卡双发),用于排障/对比。
|
||||
/// 注:本开关仅在 CIRCUIT_BREAKER_HELP_EVENT=true 时生效;后者=false 时求助卡本就是
|
||||
/// AiError 错误气泡,与停靠卡不同族,不构成双卡叠加,维持原样。
|
||||
pub const CIRCUIT_BREAKER_PARK_ONLY: bool = true;
|
||||
|
||||
// 占位配对完整性开关(解 400 orphan)。
|
||||
//
|
||||
// 单一真相源:`df_ai::context_helpers::PLACEHOLDER_INTEGRITY_ENABLED`(本模块顶部已 use)。
|
||||
@@ -2261,12 +2274,16 @@ async fn process_round_tools(
|
||||
if loop_epoch_arc.load(Ordering::SeqCst) != my_epoch {
|
||||
return ToolsOutcome::Exit;
|
||||
}
|
||||
emit_circuit_breaker_tripped(app_handle, conv_id, max_count, &sample_key);
|
||||
// #904 附项:默认只发停靠一张卡(CIRCUIT_BREAKER_PARK_ONLY),求助卡与停靠卡
|
||||
// 同轮叠加语义打架;求助卡关键信息(失败样本)内联进停靠卡 reason。开关关则退旧双卡。
|
||||
if !CIRCUIT_BREAKER_PARK_ONLY {
|
||||
emit_circuit_breaker_tripped(app_handle, conv_id, max_count, &sample_key);
|
||||
}
|
||||
// 安全停靠(设计 §四):熔断即终局,补发 AiTaskParked(原状只 emit 求助卡,
|
||||
// 托盘场景无独立事件;guard.reset 已在上方完成,现场已 save 于前一轮)。
|
||||
crate::commands::ai::emit_task_parked(
|
||||
app_handle, conv_id,
|
||||
&format!("连续同类失败 {} 次已熔断,任务安全停靠;已完成进度已保存,调整思路后重发消息可继续", max_count),
|
||||
&format!("连续同类失败 {} 次已熔断(最近错误: {}),任务安全停靠;已完成进度已保存,调整思路后重发消息可继续", max_count, sample_key),
|
||||
"circuit_breaker",
|
||||
);
|
||||
return ToolsOutcome::Exit;
|
||||
@@ -2542,15 +2559,19 @@ async fn check_stall_breaker(
|
||||
);
|
||||
let path_desc = loop_hit_path.as_deref().unwrap_or("未知路径");
|
||||
// DRY #1:双写正典(熔断求助卡跨端可见)
|
||||
emit_ai_chat_event(
|
||||
app_handle,
|
||||
AiChatEvent::AiHelpRequired {
|
||||
reason: format!("连续 {} 轮检测到对同一文件({})反复写删,疑似「写完就删」自毁循环已停止", new_loop_count, path_desc),
|
||||
context: "写文件后又删除同一文件的紧凑交替,可能在反复销毁自己的工作成果而无进展。".into(),
|
||||
options: vec!["回顾目标".into(), "换思路".into(), "停止".into()],
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
},
|
||||
);
|
||||
// #904 附项:默认只发停靠一张卡(CIRCUIT_BREAKER_PARK_ONLY),求助卡与停靠卡同轮
|
||||
// 叠加语义打架;开关关则退旧双卡行为。
|
||||
if !CIRCUIT_BREAKER_PARK_ONLY {
|
||||
emit_ai_chat_event(
|
||||
app_handle,
|
||||
AiChatEvent::AiHelpRequired {
|
||||
reason: format!("连续 {} 轮检测到对同一文件({})反复写删,疑似「写完就删」自毁循环已停止", new_loop_count, path_desc),
|
||||
context: "写文件后又删除同一文件的紧凑交替,可能在反复销毁自己的工作成果而无进展。".into(),
|
||||
options: vec!["回顾目标".into(), "换思路".into(), "停止".into()],
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
},
|
||||
);
|
||||
}
|
||||
// P1 bg 化:熔断需人工介入,系统通知补位(条件发送,失败静默)
|
||||
crate::commands::ai::notify::notify_session_event(
|
||||
app_handle,
|
||||
@@ -2573,15 +2594,19 @@ async fn check_stall_breaker(
|
||||
"[ai] G2 探索熔断:连续 {} 次检测到重复工具调用,疑似卡住停止", new_count
|
||||
);
|
||||
// DRY #1:双写正典(熔断求助卡跨端可见)
|
||||
emit_ai_chat_event(
|
||||
app_handle,
|
||||
AiChatEvent::AiHelpRequired {
|
||||
reason: format!("连续 {} 次检测到重复的工具调用(同样的 grep/读文件反复),疑似卡住已停止", new_count),
|
||||
context: "工具调用签名高度重复,可能在反复做同一件事而无进展。".into(),
|
||||
options: vec!["回顾目标".into(), "换思路".into(), "停止".into()],
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
},
|
||||
);
|
||||
// #904 附项:默认只发停靠一张卡(CIRCUIT_BREAKER_PARK_ONLY),求助卡与停靠卡同轮
|
||||
// 叠加语义打架;开关关则退旧双卡行为。
|
||||
if !CIRCUIT_BREAKER_PARK_ONLY {
|
||||
emit_ai_chat_event(
|
||||
app_handle,
|
||||
AiChatEvent::AiHelpRequired {
|
||||
reason: format!("连续 {} 次检测到重复的工具调用(同样的 grep/读文件反复),疑似卡住已停止", new_count),
|
||||
context: "工具调用签名高度重复,可能在反复做同一件事而无进展。".into(),
|
||||
options: vec!["回顾目标".into(), "换思路".into(), "停止".into()],
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
},
|
||||
);
|
||||
}
|
||||
// P1 bg 化:熔断需人工介入,系统通知补位(条件发送,失败静默)
|
||||
crate::commands::ai::notify::notify_session_event(
|
||||
app_handle,
|
||||
|
||||
Reference in New Issue
Block a user