修复: 飞书走查六项 + 停靠状态写侧根因解 + 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:
@@ -1421,20 +1421,39 @@ pub(crate) async fn execute_run_workflow_for_tool(
|
||||
// 无人值守安全停靠出口(设计 §四):五路非正常终局的统一事件+通知单点
|
||||
// ============================================================
|
||||
|
||||
/// 发停靠事件 AiTaskParked + NeedAction 系统通知(托盘场景补位,复用 notify 基建)。
|
||||
/// 无人值守安全停靠出口(设计 §四):五路非正常终局的统一「写 parked + 发事件 + 通知」三件套。
|
||||
///
|
||||
/// 调用方职责(本函数不做,保持单一只读语义):调用前完成「保存现场(落库)+
|
||||
/// #904 修:原实现只发事件+通知,漏写持久层 loop_state=parked —— 前端据此渲染停靠卡
|
||||
/// 「继续」按钮,但点继续走 ai_continue_loop 非活跃分流时查 loop_state 不匹配 → 报
|
||||
/// 「AI 未在暂停态」(全仓此前唯一写 parked 的路径是应用重启 V49 残留扫描)。现将写入
|
||||
/// 收敛进本单点,调用方无需再自行落库。
|
||||
///
|
||||
/// 调用方职责(本函数不做,保持单点语义):调用前完成「保存现场(落库)+
|
||||
/// 不保持 generating(conv_state 复位)」两步——各终局路径现状已有
|
||||
/// (teardown_round/stream_relay Fatal/ai_chat_stop),审批超时路径由本批补齐。
|
||||
/// reason 即待办清单:把停止原因与恢复指引写成人话,前端操作卡直接展示。
|
||||
pub(crate) fn emit_task_parked(app: &tauri::AppHandle, conv_id: &str, reason: &str, stop_kind: &str) {
|
||||
// ① 写持久层 loop_state=parked(停靠卡「继续」→ ai_continue_loop 分流 → recover_parked_inner
|
||||
// 的判据来源;fire-and-forget 不阻塞事件/通知链路,失败仅 warn)。
|
||||
{
|
||||
use tauri::Manager;
|
||||
let state = app.state::<crate::state::AppState>();
|
||||
let repo = state.ai_conversations.clone();
|
||||
let cid = conv_id.to_string();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Err(e) = repo.set_loop_state(&cid, Some("parked")).await {
|
||||
tracing::warn!(conv_id = %cid, error = %e, "[ai] 停靠写 loop_state=parked 失败");
|
||||
}
|
||||
});
|
||||
}
|
||||
// ② 发停靠事件(前端渲染停靠卡+继续按钮)。
|
||||
let ev = AiChatEvent::AiTaskParked {
|
||||
conversation_id: Some(conv_id.to_string()),
|
||||
reason: reason.to_string(),
|
||||
stop_kind: Some(stop_kind.to_string()),
|
||||
};
|
||||
event_bus::emit_ai_chat_event(app, ev);
|
||||
// 托盘场景补位:停靠=需用户回来决策(继续/归档),与审批挂起同用 NeedAction 分组
|
||||
// ③ 托盘场景补位:停靠=需用户回来决策(继续/归档),与审批挂起同用 NeedAction 分组
|
||||
// (主窗聚焦跳过 + 5s 节流,重复终局不会刷屏)。
|
||||
notify::notify_session_event(
|
||||
app,
|
||||
|
||||
Reference in New Issue
Block a user