修复: DeepSeek 400 全量扫描 + 队列 per-conv 隔离

- openai_compat: 扫描所有 assistant 消息剥离 orphan tool_calls(原仅查末条)
- queue 加 conversationId 字段,按会话精准 drain
- regenerate/editMessage 只清本会话排队消息
- newConversation 保留旧会话排队消息
- AiError 只清出错会话的队列项
This commit is contained in:
lxy
2026-07-20 00:19:50 +08:00
parent 42efb31bbf
commit e9e3578d26
59 changed files with 2875 additions and 1330 deletions
+67 -82
View File
@@ -352,17 +352,15 @@ pub async fn list_branches(
return Ok(serde_json::json!({ "current": "", "branches": [] }));
}
let dir = module.path.clone();
let result = std::thread::spawn(move || -> (String, Vec<serde_json::Value>) {
// git 命令在 spawn_blocking 中执行(阻塞 IO 不污染 async runtime),10s 超时
let result = tokio::task::spawn_blocking(move || -> (String, Vec<serde_json::Value>) {
// git branch --format="%(HEAD)%00%(refname:short)"
let out = std::process::Command::new("git")
.args(["branch", "--format=%(HEAD)%00%(refname:short)"])
.current_dir(&dir)
.env("LANG", "en_US.UTF-8")
.env("LC_ALL", "en_US.UTF-8")
.output()
.ok()
.and_then(|o| if o.status.success() { Some(String::from_utf8_lossy(&o.stdout).to_string()) } else { None })
.unwrap_or_default();
let out = run_git_cmd(
std::path::Path::new(&dir),
&["branch", "--format=%(HEAD)%00%(refname:short)"],
std::time::Duration::from_secs(10),
)
.unwrap_or_default();
let mut current = String::new();
let mut branches = Vec::new();
for line in out.lines() {
@@ -378,8 +376,8 @@ pub async fn list_branches(
}
(current, branches)
})
.join()
.map_err(|_| "分支列表查询线程 Join 失败".to_string())?;
.await
.map_err(|e| format!("分支列表查询任务失败: {e}"))?;
Ok(serde_json::json!({
"current": result.0,
@@ -801,26 +799,18 @@ pub async fn get_module_file_diff(
return Ok(serde_json::json!({"path": file_path, "diff": ""}));
}
// 跑 git diff <path>(工作树 vs 索引的 unstaged 变更+staged 变更)
// 设置环境变量强制 UTF-8 输出,防中文乱码
// spawn_blocking + run_git_cmd(10s 超时,防 git 卡死阻塞 runtime)
let cmd_dir = root.clone();
let path_arg = file_path.replace('\\', "/");
let diff = std::thread::spawn(move || -> Option<String> {
let out = std::process::Command::new("git")
.args(["diff", "--", &path_arg])
.current_dir(&cmd_dir)
.env("LANG", "en_US.UTF-8")
.env("LC_ALL", "en_US.UTF-8")
.env("GIT_PAGER", "cat")
.output()
.ok()?;
if out.status.success() && !out.stdout.is_empty() {
Some(String::from_utf8_lossy(&out.stdout).to_string())
} else {
None
}
let diff = tokio::task::spawn_blocking(move || -> Option<String> {
run_git_cmd(
&cmd_dir,
&["diff", "--", &path_arg],
std::time::Duration::from_secs(10),
)
})
.join()
.map_err(|_| "git diff 线程 Join 失败".to_string())?
.await
.map_err(|e| format!("git diff 任务失败: {e}"))?
.unwrap_or_default();
Ok(serde_json::json!({
"path": file_path.replace('\\', "/"),
@@ -879,15 +869,19 @@ pub async fn scan_project_modules(
if !child_path.join(".git").exists() { continue; }
let child_path_str = child_path.to_string_lossy().replace("\\", "/");
if existing_paths.contains(&child_path_str.to_lowercase()) { continue; }
// 获取远程地址(失败忽略)
let git_url = std::process::Command::new("git")
.args(["remote", "get-url", "origin"])
.current_dir(&child_path)
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.filter(|s| !s.is_empty());
// 获取远程地址(失败忽略);spawn_blocking + run_git_cmd(10s 超时,防 git 卡死阻塞 runtime)
let url_dir = child_path.clone();
let git_url = tokio::task::spawn_blocking(move || {
run_git_cmd(
&url_dir,
&["remote", "get-url", "origin"],
std::time::Duration::from_secs(10),
)
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
})
.await
.unwrap_or(None);
let now_str = now_millis();
let record = ProjectModuleRecord {
id: new_id(),
@@ -976,22 +970,19 @@ pub async fn get_module_commits(
let fetch_plus = fetch + 1;
let dir = module.path.clone();
let dir_for_git = dir.clone();
let commits: Vec<serde_json::Value> = std::thread::spawn(move || -> Vec<serde_json::Value> {
let out = std::process::Command::new("git")
.args([
// git 命令在 spawn_blocking 中执行(阻塞 IO 不污染 async runtime),10s 超时
let commits: Vec<serde_json::Value> = tokio::task::spawn_blocking(move || -> Vec<serde_json::Value> {
let out = run_git_cmd(
std::path::Path::new(&dir_for_git),
&[
"log",
&format!("--skip={}", skip),
&format!("-{}", fetch_plus),
"--format=%h %ct %an %s",
])
.current_dir(&dir_for_git)
.env("LANG", "en_US.UTF-8")
.env("LC_ALL", "en_US.UTF-8")
.output()
.ok()
.and_then(|o| if o.status.success() { Some(o.stdout) } else { None })
.map(|b| String::from_utf8_lossy(&b).to_string())
.unwrap_or_default();
],
std::time::Duration::from_secs(10),
)
.unwrap_or_default();
let mut commits: Vec<serde_json::Value> = Vec::new();
for line in out.lines() {
let line = line.trim();
@@ -1014,8 +1005,8 @@ pub async fn get_module_commits(
}
commits
})
.join()
.map_err(|_| "提交历史查询线程 Join 失败".to_string())?;
.await
.map_err(|e| format!("提交历史查询任务失败: {e}"))?;
// 判断 has_more:取了 N+1 条但只返回 N 条,说明有更多
let total_fetched = commits.len();
@@ -1055,17 +1046,17 @@ pub async fn get_commit_detail(
return Ok(serde_json::json!({ "files": [], "diff": "" }));
}
let dir = module.path.clone();
let (files, diff, parents, author, date, full_message) = std::thread::spawn(move || -> (Vec<serde_json::Value>, String, Vec<String>, String, String, String) {
// git 命令在 spawn_blocking 中执行(阻塞 IO 不污染 async runtime),10s 超时
let (files, diff, parents, author, date, full_message) = tokio::task::spawn_blocking(move || -> (Vec<serde_json::Value>, String, Vec<String>, String, String, String) {
let dir_path = std::path::Path::new(&dir);
let timeout = std::time::Duration::from_secs(10);
// 1) 获取变更文件列表:`git diff-tree --no-commit-id -r --name-status <hash>`
let files_out = std::process::Command::new("git")
.args(["diff-tree", "--no-commit-id", "-r", "--name-status", &commit_hash])
.current_dir(&dir)
.env("LANG", "en_US.UTF-8")
.env("LC_ALL", "en_US.UTF-8")
.output()
.ok()
.and_then(|o| if o.status.success() { Some(String::from_utf8_lossy(&o.stdout).to_string()) } else { None })
.unwrap_or_default();
let files_out = run_git_cmd(
dir_path,
&["diff-tree", "--no-commit-id", "-r", "--name-status", &commit_hash],
timeout,
)
.unwrap_or_default();
let mut files: Vec<serde_json::Value> = Vec::new();
for line in files_out.lines() {
let line = line.trim();
@@ -1079,26 +1070,20 @@ pub async fn get_commit_detail(
}
}
// 2) 获取全量 diff:`git show <hash>`(仅 diff 部分)
let diff = std::process::Command::new("git")
.args(["show", "--format=", &commit_hash])
.current_dir(&dir)
.env("LANG", "en_US.UTF-8")
.env("LC_ALL", "en_US.UTF-8")
.output()
.ok()
.and_then(|o| if o.status.success() { Some(String::from_utf8_lossy(&o.stdout).to_string()) } else { None })
.unwrap_or_default();
let diff = run_git_cmd(
dir_path,
&["show", "--format=", &commit_hash],
timeout,
)
.unwrap_or_default();
// 3) 获取提交元信息(父提交/作者/日期/完整消息):
// 用 printf 自定义格式,%P=父哈希(空格分隔多个)\t%an=作者\t%ad=日期\t%B=完整消息
let meta = std::process::Command::new("git")
.args(["show", "-s", "--format=%P\t%an\t%ad\t%B", &commit_hash])
.current_dir(&dir)
.env("LANG", "en_US.UTF-8")
.env("LC_ALL", "en_US.UTF-8")
.output()
.ok()
.and_then(|o| if o.status.success() { Some(String::from_utf8_lossy(&o.stdout).to_string()) } else { None })
.unwrap_or_default();
let meta = run_git_cmd(
dir_path,
&["show", "-s", "--format=%P\t%an\t%ad\t%B", &commit_hash],
timeout,
)
.unwrap_or_default();
let mut parents = Vec::new();
let mut author = String::new();
let mut date = String::new();
@@ -1121,8 +1106,8 @@ pub async fn get_commit_detail(
}
(files, diff, parents, author, date, full_message)
})
.join()
.map_err(|_| "提交详情查询线程 Join 失败".to_string())?;
.await
.map_err(|e| format!("提交详情查询任务失败: {e}"))?;
Ok(serde_json::json!({
"files": files,