优化: UI/UX遗留批(审计/AI命令层/前端组件,会话前基线收尾)
- AuditLog +298(筛选/详情/i18n)+ audit 后端 record/mod - AI 命令层:generate_image +81 / fetch_url / fetch_search / skills / tool_registry / tools/file / provider / conversation - 前端组件:AiChat/TopBar/ConversationSidebar/GitChanges/ApprovalPopup/Dashboard/ProjectDetail 等 30+ + composables + i18n - 诊断文档: aichat历史会话实证诊断-2026-08-04 + project_soft_delete 测试
This commit is contained in:
@@ -150,13 +150,13 @@ pub fn register(
|
||||
// 虽 1MB 字节上限挡住极端情况,但万行级源码全量进 LLM context 仍易撑爆。
|
||||
// 改:无 offset 默认返回前 500 行 + has_more 提示翻页(对齐 read 工具常规用法)。
|
||||
let line_count = content.lines().count();
|
||||
let (result, offset_used, has_more) = if let Some(offset) = args["offset"].as_u64() {
|
||||
let (result, offset_used, has_more, limit) = if let Some(offset) = args["offset"].as_u64() {
|
||||
let lines: Vec<&str> = content.lines().collect();
|
||||
let skip = offset as usize;
|
||||
let limit = args["limit"].as_u64().unwrap_or(200).min(2000) as usize;
|
||||
let page: Vec<&str> = lines.into_iter().skip(skip).take(limit).collect();
|
||||
let more = (skip + page.len()) < line_count;
|
||||
(page.join("\n"), Some(skip), more)
|
||||
(page.join("\n"), Some(skip), more, limit)
|
||||
} else {
|
||||
// 无 offset: 尊重 LLM 传入的 limit(对齐有 offset 分支语义),
|
||||
// 未传 limit 默认 500 行(大文件翻页友好,避免一次灌入全量)。
|
||||
@@ -168,13 +168,26 @@ pub fn register(
|
||||
.min(2000) as usize;
|
||||
let page: Vec<&str> = content.lines().take(limit).collect();
|
||||
let more = line_count > page.len();
|
||||
(page.join("\n"), None, more)
|
||||
(page.join("\n"), None, more, limit)
|
||||
};
|
||||
// 截断明文化:治模型把 has_more 当"被压缩/损坏"反复重读同文件(实证 read_file 同文件重复读 6 次)。
|
||||
// returned_lines=本次返回行数,consumed=累计已读(offset+本次),truncated_info 明确剩余行数+续读方式。
|
||||
let returned_lines = result.lines().count();
|
||||
let consumed = offset_used.unwrap_or(0) + returned_lines;
|
||||
let truncated_info = if has_more {
|
||||
Some(format!(
|
||||
"文件共 {line_count} 行,本次返回 {returned_lines} 行(offset={}, limit={limit}),剩余 {} 行未读。继续读取请用 offset={} 参数(可配合 limit={limit})",
|
||||
offset_used.map(|o| o.to_string()).unwrap_or_else(|| "0".to_string()),
|
||||
line_count - consumed,
|
||||
consumed,
|
||||
))
|
||||
} else { None };
|
||||
Ok(serde_json::json!({
|
||||
"path": path, "content": result, "size": metadata.len(), "file_hash": file_hash, "lines": line_count,
|
||||
"offset": offset_used,
|
||||
"returned_lines": result.lines().count(),
|
||||
"has_more": has_more,
|
||||
"returned_lines": returned_lines,
|
||||
"truncated": has_more,
|
||||
"truncated_info": truncated_info,
|
||||
}))
|
||||
}
|
||||
);
|
||||
@@ -1034,7 +1047,7 @@ pub fn register(
|
||||
registry,
|
||||
dummy: Arc<()>,
|
||||
"run_command",
|
||||
"在指定工作目录执行 shell 命令,返回 stdout/stderr/exit_code。仅用于命令执行场景:跑测试套件、构建项目、运行二进制/脚本验证行为。读取文件用 read_file,编辑文件用 patch_file/write_file,列目录用 list_directory,搜索文件名用 search_files——不要用本工具完成这些操作。高风险,须人工批准。命令需自包含(非交互式,避免需用户输入的程序)。默认超时 60 秒。",
|
||||
"在指定工作目录执行 shell 命令,返回 stdout/stderr/exit_code。仅用于命令执行场景:跑测试套件、构建项目、运行二进制/脚本验证行为。读取文件用 read_file,编辑文件用 patch_file/write_file,列目录用 list_directory,搜索文件名用 search_files——不要用本工具完成这些操作。高风险,须人工批准。命令需自包含(非交互式,避免需用户输入的程序)。默认超时 60 秒。返回含 succeeded 布尔(按 exit_code==0 判定)。注意:部分命令非零退出属正常语义(如 git diff 无差异返回 1),此时以 stdout/stderr 内容为准,勿仅凭 exit_code≠0 判失败。",
|
||||
RiskLevel::High,
|
||||
schema: object_schema(vec![
|
||||
("command", "string", true),
|
||||
@@ -1108,6 +1121,7 @@ pub fn register(
|
||||
let (stderr, stderr_trunc) = truncate_output(&result.stderr, MAX_OUT);
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"succeeded": result.exit_code == Some(0),
|
||||
"command": command,
|
||||
"working_dir": working_dir,
|
||||
"exit_code": result.exit_code,
|
||||
@@ -1289,11 +1303,15 @@ mod tests {
|
||||
let win_start = char_start.saturating_sub(context_chars);
|
||||
let win_end = (char_start + context_chars).min(total_chars);
|
||||
let window: String = line.chars().skip(win_start).take(win_end - win_start).collect();
|
||||
// 结构:window 前可加前导 …(仅 win_start>0,match 行首不加),窗口标记附加在 window 后。
|
||||
// 修复:原 format 在 window 前有字面量 …(±N字符窗口)…,致 match 行首(win_start=0)
|
||||
// 也以 … 开头,违反「行首不前导 …」语义。
|
||||
let lead = if win_start > 0 { "…" } else { "" };
|
||||
format!(
|
||||
"{}…(±{}字符窗口)…{}",
|
||||
if win_start > 0 { "…" } else { "" },
|
||||
context_chars,
|
||||
window
|
||||
"{lead}{window}…(±{context_chars}字符窗口)…",
|
||||
lead = lead,
|
||||
window = window,
|
||||
context_chars = context_chars
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user