//! DevFlow Tauri 入口 — 初始化 AppState 并注册全部 IPC 命令 mod commands; mod state; use tauri::{Emitter, Listener, Manager}; use state::AppState; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_window_state::Builder::default().build()) .setup(|app| { // 数据库放在系统应用数据目录 下(dev/release 拆分): // Dev 模式 devflow-dev.db(可随意改动/清空),Build 模式 devflow.db(长期保留真实运行数据) let db_name = if cfg!(debug_assertions) { "devflow-dev.db" } else { "devflow.db" }; let data_dir = app.path().app_data_dir()?; std::fs::create_dir_all(&data_dir)?; let db_path = data_dir.join(db_name); // 初始化全局状态(打开数据库 + 执行迁移) let app_state = tauri::async_runtime::block_on(AppState::init(&db_path))?; // FR-S1:启动一次性迁移 DB 明文 api_key → OS keyring(失败保留明文下次重试,非阻断) if let Err(e) = tauri::async_runtime::block_on( commands::ai::secret::migrate_secrets_to_keyring(&app_state.ai_providers), ) { tracing::warn!("[FR-S1] 启动密钥迁移失败(非阻断): {}", e); } // B-260616-01: L0 握手 — 提前 clone ai_session(Arc),因 manage() 会 move app_state let session_for_handshake = app_state.ai_session.clone(); app.manage(app_state); // B-260616-01: L0 握手 — 监听前端就绪事件,清除 HMR/刷新导致的残留 generating 状态 let app_handle = app.handle().clone(); app.listen("ai-client-ready", move |_event| { let session_arc = session_for_handshake.clone(); let app_h = app_handle.clone(); tauri::async_runtime::spawn(async move { let mut session = session_arc.lock().await; // F-260616-09 B 批8(设计 §3 batch8 + §5.2):遍历 per_conv(HashMap)清多 conv // 残留 generating(HMR/dev 热载场景多 conv 并发跑 loop 致多 conv 卡 generating)。 // 批4:per_conv 唯一真相源,删顶层 session.generating 双写复位(顶层字段已退役)。 let dirty_convs: Vec = session .per_conv .iter() .filter(|(_, c)| c.generating) .map(|(id, _)| id.clone()) .collect(); let was_generating = !dirty_convs.is_empty(); // 复位每个残留 generating 的 conv(批8 多 conv 全覆盖)。 for cid in &dirty_convs { if let Some(conv) = session.per_conv.get_mut(cid) { conv.generating = false; } } // BUG-260619-06 修复: clear 致冷启动 restore 重建审批丢失(restore 填充后 clear 无条件清空, // 重启后待审批工具全丢)。改 retain 仅清非 recovered(本次会话/HMR 死 pending), // 保留 restore 重建(recovered=true,audit.rs:331),对齐 switchConversation retain 保护意图。 session.pending_approvals.retain(|_, a| !a.recovered); drop(session); if was_generating { // 补偿事件:每个残留 conv 各发一个 AiCompleted(按 conversation_id 路由), // 前端 useAiEvents.ts:133-140 按 conv_id 各归各复位 streaming/generatingConvId。 for cid in &dirty_convs { let _ = app_h.emit( "ai-chat-event", commands::ai::AiChatEvent::AiCompleted { total_tokens: 0, prompt_tokens: 0, completion_tokens: 0, incomplete: None, conversation_id: Some(cid.clone()), }, ); } } tracing::info!( "[L0-handshake] 前端重连握手完成, was_generating={}, dirty_convs={:?}", was_generating, dirty_convs ); }); }); Ok(()) }) .invoke_handler(tauri::generate_handler![ // 项目 commands::project::list_projects, commands::project::create_project, commands::project::import_project, commands::project::get_project, commands::project::update_project, commands::project::delete_project, commands::project::list_deleted_projects, commands::project::restore_project, commands::project::purge_project, commands::project::scan_project_stack, commands::project::check_path_binding, commands::project::relocate_project_path, commands::project::check_path_exists, commands::project::scan_project_with_ai, commands::project::scan_directory_for_projects, commands::project::import_projects_batch, // 任务 commands::task::list_tasks, commands::task::create_task, commands::task::update_task, commands::task::delete_task, commands::task::restore_task, commands::task::get_task_by_id, commands::task::advance_task, // 灵感 commands::idea::list_ideas, commands::idea::create_idea, commands::idea::update_idea, commands::idea::delete_idea, commands::idea::evaluate_idea, commands::idea::promote_idea, // 工作流 commands::workflow::run_workflow, commands::workflow::list_workflow_executions, commands::workflow::get_workflow_execution, commands::workflow::approve_human_approval, commands::workflow::cancel_workflow_node, // AI 聊天 commands::ai::ai_chat_send, commands::ai::ai_regenerate, commands::ai::ai_chat_edit, commands::ai::ai_chat_force_send, commands::ai::ai_chat_stop, commands::ai::ai_approve, // F-260616-03:达 max_iterations 暂停态续/停(消费 AiMaxRoundsReached,前端操作卡留 batch45) commands::ai::ai_continue_loop, commands::ai::ai_stop_loop, commands::ai::ai_pending_tool_calls, commands::ai::ai_chat_clear, commands::ai::ai_is_generating, // F-15 阶段2 手动上下文管理:分段归档 + LLM 压缩 commands::ai::ai_chat_clear_context, commands::ai::ai_chat_compress_context, commands::ai::ai_list_providers, commands::ai::ai_save_provider, commands::ai::ai_set_provider, commands::ai::ai_delete_provider, // F-260614-04c: 负载均衡池可编辑层(enabled/weight 轻量更新 + caps 重建) commands::ai::ai_update_provider_pool, // F-01 阶段5:测试连接拉取模型列表 + 单模型探测 commands::ai::ai_fetch_models, commands::ai::ai_probe_model, // AI 对话管理 commands::ai::ai_conversation_create, commands::ai::ai_conversation_list, commands::ai::ai_conversation_switch, commands::ai::ai_conversation_delete, commands::ai::ai_conversation_rename, commands::ai::ai_conversation_archive, commands::ai::ai_conversation_set_pinned, commands::ai::ai_conversation_export, commands::ai::ai_list_skills, commands::ai::ai_set_concurrency_config, commands::ai::ai_set_agent_max_iterations, commands::ai::ai_set_agent_max_retries, // F-260619-03 Phase A: AI 工具文件访问授权目录白名单 commands::ai::ai_get_allowed_dirs, commands::ai::ai_set_allowed_dirs, // F-260619-03 Phase B: 路径授权弹窗决策(消费 AiDirAuthRequired 挂起) commands::ai::ai_authorize_dir, // 审批历史面板(AE-2025-08:查 ai_tool_executions 表,敏感字段截断) commands::ai::audit::list_tool_executions, // 知识库 commands::knowledge::knowledge_list, commands::knowledge::knowledge_get, commands::knowledge::knowledge_search, commands::knowledge::knowledge_create, commands::knowledge::knowledge_update_status, commands::knowledge::knowledge_record_reuse, commands::knowledge::knowledge_list_candidates, commands::knowledge::knowledge_archive, commands::knowledge::knowledge_get_config, commands::knowledge::knowledge_save_config, commands::knowledge::knowledge_extract_now, commands::knowledge::knowledge_get_detail, commands::knowledge::knowledge_update, commands::knowledge::knowledge_events, // 通用应用设置 KV(前端 localStorage 迁移目标) commands::settings::settings_get, commands::settings::settings_set, commands::settings::settings_get_all, commands::settings::settings_delete, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }