1212use base64:: Engine ;
1313use tauri:: Emitter ;
1414
15- use crate :: ai_chat:: { AiTurn , trajectory_to_json } ;
15+ use crate :: ai_chat:: AiTurn ;
1616use crate :: ai_client:: AiClient ;
1717use crate :: ai_mock:: AiMockAdapter ;
1818use crate :: ai_note_refine:: AiNoteRefineAdapter ;
@@ -32,6 +32,12 @@ mod workers;
3232/// 子模块再导出:既有 `crate::ai_refine_task::{…}` 导入路径保持不变。
3333pub ( crate ) use self :: workers:: { refine_slices_concurrent, RefineCtx } ;
3434
35+ /// 任务终态骨架子模块(catch_unwind 归一 + 落库/审计/裁剪,锁序见 skeleton.rs 模块头)
36+ #[ path = "ai_refine_task/skeleton.rs" ]
37+ mod skeleton;
38+ /// 子模块再导出:既有 `crate::ai_refine_task::{…}` 导入路径保持不变。
39+ pub ( crate ) use self :: skeleton:: run_refine_task_skeleton;
40+
3541/// 切片并发上限(REQ-145:并发 2-3——配额并发安全由 command 层启动前按
3642/// 预估片数一次性消耗保证,此处 worker 数不超切片数)。v0.17.0:pub(crate)
3743/// ——笔记级精修任务共用同一上限。
@@ -68,104 +74,6 @@ fn emit_refine_stream(st: &AppState, task_id: u64, frame: RefineStreamFrame) {
6874 let _ = st. app . emit ( "ai:refine-stream" , RefineStreamPayload { task_id, frame } ) ;
6975}
7076
71- /// 精修任务审计记录(F1:REQ-140 轨迹可见化——summary 不含原文,隐私红线)。
72- /// v0.17.0:summary_ctx 泛化("session=1"/"note=3"——会话级/笔记级共用)。
73- fn push_refine_audit ( st : & AppState , summary_ctx : & str , result : & str , model : Option < & str > ) {
74- let now = crate :: db_sessions_rows:: unix_seconds ( ) ;
75- if let Ok ( mut g) = st. ai_guardrails . lock ( ) {
76- g. push_audit ( crate :: ai_guardrails:: AiAuditEntry {
77- at_unix : now,
78- upload_summary : format ! ( "refine {} model={}" , summary_ctx, model. unwrap_or( "?" ) ) ,
79- result : result. to_string ( ) ,
80- } ) ;
81- }
82- }
83-
84- /// 任务收尾公共骨架(会话级/笔记级共用——v0.17.0 REQ-246 提取)。
85- ///
86- /// @ai-context: 彻底检测加固(2026-08-21):spawn_blocking 的 JoinHandle 未被
87- /// await——闭包内 panic 会被 tokio 吞掉,任务状态永久停在
88- /// Pending(前端永久显示"任务排队中",无失败可重试)。
89- /// catch_unwind 把 panic 归一为 Failed 状态,状态流转永不失联。
90- /// @ai-context: F2-B4:单片失败重试后仍失败 → 保留已成功片(partial_failed
91- /// 语义:failed_slices > 0,前端显示"部分成功 x/y 片")。
92- pub ( crate ) fn run_refine_task_skeleton (
93- st : AppState ,
94- task_id : u64 ,
95- target_summary : String ,
96- work : impl FnOnce ( ) -> Result < ( AiRefineResult , Vec < AiTurn > ) , AiTaskFailure > ,
97- ) {
98- let started = std:: time:: Instant :: now ( ) ;
99- let outcome = std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( work) ) . unwrap_or_else ( |_| {
100- Err ( AiTaskFailure :: Other (
101- "精修任务内部错误(panic)——请重试;若复现请反馈" . to_string ( ) ,
102- ) )
103- } ) ;
104- let elapsed_ms = started. elapsed ( ) . as_millis ( ) as i64 ;
105- match outcome {
106- Ok ( ( result, turns) ) => {
107- eprintln ! (
108- "[refine-task] task={} succeeded slices={} failed={} diff={}" ,
109- task_id,
110- result. slices,
111- result. failed_slices,
112- result. diff. len( )
113- ) ;
114- // v0.16.0(REQ-230):轨迹落库(提示词/回答全文——任务对话视图数据源)
115- if let Some ( json) = trajectory_to_json ( & turns) {
116- if let Err ( e) = st. db . update_ai_task_trajectory ( task_id, & json) {
117- eprintln ! ( "[refine-task] task={} 轨迹落库失败(不阻断): {}" , task_id, e) ;
118- }
119- }
120- {
121- let mut tasks = st. ai_tasks . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
122- if let Some ( entry) = tasks. get_mut ( & task_id) {
123- entry. result = serde_json:: to_value ( & result) . ok ( ) ;
124- }
125- }
126- set_task ( & st, task_id, AiTaskState :: Succeeded ) ;
127- // REQ-247:终态帧(前端消息定格为最终摘要+双入口)
128- emit_refine_stream ( & st, task_id, RefineStreamFrame :: Done {
129- slices : result. slices ,
130- failed_slices : result. failed_slices ,
131- } ) ;
132- // F1 修复(2026-08-21):精修调用上审计——REQ-140 轨迹可见化
133- push_refine_audit ( & st, & target_summary, "ok" , Some ( & result. model ) ) ;
134- // F2 任务中心:终态落库(写库失败不阻断——H2 设计)+ 保留策略
135- // 裁剪(审查修复:trim 原只在启动时跑,运行期终态任务会累积——
136- // 每次终态后清理超限旧终态,防表膨胀)
137- let result_json = serde_json:: to_string ( & result) . ok ( ) ;
138- let _ = st. db . finish_ai_task (
139- task_id,
140- "succeeded" ,
141- result_json. as_deref ( ) ,
142- None ,
143- elapsed_ms,
144- ) ;
145- let _ = st. db . trim_ai_tasks ( ) ;
146- }
147- Err ( reason) => {
148- // 打印具体 message——区分"未配置密钥"vs"密钥无效(401/403)"(真机排查)
149- eprintln ! (
150- "[refine-task] task={} failed kind={} msg={}" ,
151- task_id,
152- reason. kind( ) ,
153- reason. message( )
154- ) ;
155- set_task ( & st, task_id, AiTaskState :: Failed { reason : reason. clone ( ) } ) ;
156- push_refine_audit ( & st, & target_summary, "error" , None ) ;
157- let _ = st. db . finish_ai_task (
158- task_id,
159- "failed" ,
160- None ,
161- Some ( & format ! ( "{}: {}" , reason. kind( ) , reason. message( ) ) ) ,
162- elapsed_ms,
163- ) ;
164- let _ = st. db . trim_ai_tasks ( ) ;
165- }
166- }
167- }
168-
16977/// 后台精修任务:规则草稿 → 切片 → 逐片精修(mock/云端)→ 合并 → diff。
17078///
17179/// @ai-context: v0.17.0(REQ-245):dims=策略解析结果(command 层 resolve 后
0 commit comments