重构: 对话透明化 L1 + ③.2 审批拆分 + ⑥.4 @展开 + DAG 展示
修复 tool_result 压缩零效果(BUG-260628-01):单行/少行/JSON 大字符 串字段逃逸压缩的问题,基于实测数据(53/94 次迭代零节省)调参, 增加字符级保底截断,压缩有效率从 8% 提升至 ~85%+
This commit is contained in:
@@ -529,7 +529,6 @@ pub async fn ai_approve(
|
||||
tool_call_id: String,
|
||||
approved: bool,
|
||||
) -> Result<String, String> {
|
||||
authz_debug(&format!("[ai_approve] 入口 tool_call_id={} approved={}", tool_call_id, approved));
|
||||
let mut session = state.ai_session.lock().await;
|
||||
|
||||
let approval = match session.pending_approvals.remove(&tool_call_id) {
|
||||
@@ -756,13 +755,7 @@ pub async fn ai_approve(
|
||||
|
||||
/// F-260620 临时诊断:授权/审批 IPC 调用链文件日志(不依赖终端 stderr,排障用)。
|
||||
/// 写入 OS 临时目录(跨平台,不污染用户项目目录)。打包分发后仍可工作。
|
||||
fn authz_debug(msg: &str) {
|
||||
use std::io::Write;
|
||||
let log_path = std::env::temp_dir().join("devflow-authz-debug.log");
|
||||
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&log_path) {
|
||||
let _ = writeln!(f, "{}", msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 阶段4(容错/恢复):取路径的 Windows 盘符(如 "C:" / "E:"),非 Windows / 无盘符返 None。
|
||||
///
|
||||
@@ -852,7 +845,6 @@ pub async fn ai_authorize_dir(
|
||||
decision = %decision,
|
||||
"[AI-AUTHZ] ai_authorize_dir 入口"
|
||||
);
|
||||
authz_debug(&format!("[ai_authorize_dir] 入口 tool_call_id={} decision={}", tool_call_id, decision));
|
||||
// 取 pending(阶段3a 单真相源合并:从 pending_approvals remove;kind 校验为 Path)。
|
||||
let approval = {
|
||||
let mut session = state.ai_session.lock().await;
|
||||
|
||||
@@ -221,12 +221,17 @@ pub async fn ai_conversation_list(
|
||||
let models: Vec<String> = r.models.as_deref()
|
||||
.and_then(|s| serde_json::from_str(s).ok())
|
||||
.unwrap_or_default();
|
||||
let pinned_goals: Vec<String> = r.pinned_goals
|
||||
.as_deref()
|
||||
.and_then(|s| serde_json::from_str(s).ok())
|
||||
.unwrap_or_default();
|
||||
serde_json::json!({
|
||||
"id": r.id,
|
||||
"title": r.title,
|
||||
"provider_id": r.provider_id,
|
||||
"model": r.model,
|
||||
"models": models,
|
||||
"pinned_goals": pinned_goals,
|
||||
"archived": r.archived,
|
||||
"pinned": r.pinned,
|
||||
"prompt_tokens": r.prompt_tokens,
|
||||
@@ -311,6 +316,12 @@ pub async fn ai_conversation_switch(
|
||||
conv.agent_language = None;
|
||||
conv.iteration_used = 0;
|
||||
conv.stop_flag.store(false, Ordering::SeqCst);
|
||||
// 从 DB 恢复 pinned_goals 到 per_conv(G1 目标钉扎持久化)
|
||||
let pinned_goals: Vec<String> = record.pinned_goals
|
||||
.as_deref()
|
||||
.and_then(|s| serde_json::from_str(s).ok())
|
||||
.unwrap_or_default();
|
||||
conv.pinned_goals = pinned_goals;
|
||||
}
|
||||
// 仅清空目标对话自身的挂起审批,保留其他对话的(防 init 重建的内存 HashMap 被清空,
|
||||
// 重启恢复链路:restore_pending_approvals(init 重建) → switchConversation(此处不清目标对话的)
|
||||
@@ -439,6 +450,30 @@ pub async fn ai_conversation_set_pinned(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 更新对话目标钉扎列表(G1 目标钉扎持久化:对话透明化 L1)
|
||||
///
|
||||
/// 接收前端 UI 增删后的目标列表,持久化到 DB 并同步更新内存 per_conv 状态。
|
||||
/// goals 是完整替换(非增量),前端增/删后传全量 new Vec。
|
||||
#[tauri::command]
|
||||
pub async fn ai_update_conversation_goals(
|
||||
state: State<'_, AppState>,
|
||||
conversation_id: String,
|
||||
goals: Vec<String>,
|
||||
) -> Result<(), String> {
|
||||
let goals_json = serde_json::to_string(&goals).map_err(|e| format!("序列化目标列表失败: {e}"))?;
|
||||
// 写 DB
|
||||
state.ai_conversations
|
||||
.update_field(&conversation_id, "pinned_goals", &goals_json)
|
||||
.await
|
||||
.map_err(err_str)?;
|
||||
// 同步内存 per_conv(若已加载)
|
||||
let mut session = state.ai_session.lock().await;
|
||||
if let Some(conv) = session.per_conv.get_mut(&conversation_id) {
|
||||
conv.pinned_goals = goals;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 导出对话为指定格式(UX-18:对话导出)
|
||||
///
|
||||
/// - 优先落库 messages(完整历史,与 switch 一致),内存 session 不读(可能被切走/未落库)
|
||||
|
||||
@@ -164,7 +164,7 @@ pub(crate) async fn save_conversation(
|
||||
// loop 内 save 由 run_agentic_loop 入参 conv_id 透传;IPC 路径(commands.rs)save 也传 conv_id。
|
||||
// conv() 惰性建:save 路径 conv 必然已建(send/regenerate/edit/switch 均先 conv());若极端
|
||||
// 未建(如启动恢复无 live conv),conv() 建空 PerConvState,save 空 messages(幂等不污染)。
|
||||
let (persist_msgs, provider_id, created_at) = {
|
||||
let (persist_msgs, provider_id, created_at, pinned_goals) = {
|
||||
let mut session = session_arc.lock().await;
|
||||
let mut msgs = session.conv(conv_id).messages.all_messages_clone();
|
||||
for m in &mut msgs {
|
||||
@@ -185,6 +185,7 @@ pub(crate) async fn save_conversation(
|
||||
msgs,
|
||||
session.active_provider_id.clone(),
|
||||
session.active_conv_created_at.clone(),
|
||||
session.conv(conv_id).pinned_goals.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -224,6 +225,8 @@ pub(crate) async fn save_conversation(
|
||||
if !list.iter().any(|x| x == m) { list.push(m.to_string()); }
|
||||
rec.models = Some(serde_json::to_string(&list).unwrap_or_else(|_| "[]".to_string()));
|
||||
}
|
||||
// G1 目标钉扎持久化:将 per_conv.pinned_goals 写入 DB
|
||||
rec.pinned_goals = Some(serde_json::to_string(&pinned_goals).unwrap_or_else(|_| "[]".to_string()));
|
||||
// update_full 仍写 messages 列(保留旧值,本批不改 messages 字段),写元数据 + updated_at
|
||||
if let Err(e) = conv_repo.update_full(&rec).await {
|
||||
tracing::warn!("更新对话元数据失败 {conv_id}: {e}");
|
||||
@@ -250,6 +253,7 @@ pub(crate) async fn save_conversation(
|
||||
pinned: false,
|
||||
prompt_tokens: usage.map(|u| u.prompt_tokens as i64),
|
||||
completion_tokens: usage.map(|u| u.completion_tokens as i64),
|
||||
pinned_goals: Some(serde_json::to_string(&pinned_goals).unwrap_or_else(|_| "[]".to_string())),
|
||||
created_at: conv_created.clone(),
|
||||
updated_at: now,
|
||||
};
|
||||
|
||||
@@ -339,6 +339,7 @@ pub fn run() {
|
||||
commands::ai::ai_conversation_rename,
|
||||
commands::ai::ai_conversation_archive,
|
||||
commands::ai::ai_conversation_set_pinned,
|
||||
commands::ai::ai_update_conversation_goals,
|
||||
commands::ai::ai_conversation_export,
|
||||
commands::ai::ai_list_skills,
|
||||
// 核心设计6: 热重载技能(invalidate + 重扫,不重启生效)
|
||||
|
||||
Reference in New Issue
Block a user