修复警告

This commit is contained in:
xintaofei
2026-03-19 21:33:21 +08:00
parent f6fd3da401
commit 2712bd1f91
2 changed files with 30 additions and 46 deletions

View File

@@ -159,7 +159,7 @@ async fn build_agent(
// instead of appending to the previous one. // instead of appending to the previous one.
if runtime_env if runtime_env
.get("OPENCLAW_RESET_SESSION") .get("OPENCLAW_RESET_SESSION")
.map_or(false, |v| v == "1") .is_some_and(|v| v == "1")
{ {
parts.push("--reset-session".into()); parts.push("--reset-session".into());
} }

View File

@@ -1,7 +1,7 @@
use std::collections::{hash_map::DefaultHasher, HashMap, HashSet}; use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::io::{Read, Write}; use std::io::Write;
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::sync::{mpsc, LazyLock, Mutex}; use std::sync::{mpsc, LazyLock, Mutex};
@@ -134,7 +134,6 @@ pub enum FileTreeNode {
pub struct FilePreviewContent { pub struct FilePreviewContent {
pub path: String, pub path: String,
pub content: String, pub content: String,
pub truncated: bool,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@@ -144,7 +143,6 @@ pub struct FileEditContent {
pub etag: String, pub etag: String,
pub mtime_ms: Option<i64>, pub mtime_ms: Option<i64>,
pub readonly: bool, pub readonly: bool,
pub truncated: bool,
pub line_ending: String, pub line_ending: String,
} }
@@ -208,7 +206,7 @@ async fn detect_conflicts(path: &str) -> Result<Vec<String>, AppCommandError> {
Ok(String::from_utf8_lossy(&output.stdout) Ok(String::from_utf8_lossy(&output.stdout)
.lines() .lines()
.map(|l| unquote_git_path(l)) .map(unquote_git_path)
.filter(|l| !l.is_empty()) .filter(|l| !l.is_empty())
.collect()) .collect())
} }
@@ -1819,11 +1817,10 @@ pub async fn git_continue_operation(
const WATCH_IGNORED_DIRS: &[&str] = &["__pycache__"]; const WATCH_IGNORED_DIRS: &[&str] = &["__pycache__"];
const FILE_TREE_IGNORED_DIRS: &[&str] = &[".git", "__pycache__"]; const FILE_TREE_IGNORED_DIRS: &[&str] = &[".git", "__pycache__"];
const FILE_PREVIEW_DEFAULT_MAX_BYTES: usize = 200_000; /// Hard limit: refuse to open files larger than 50 MB in the text editor.
const FILE_PREVIEW_MIN_BYTES: usize = 4_096; const FILE_OPEN_HARD_LIMIT: usize = 50_000_000;
const FILE_PREVIEW_MAX_BYTES: usize = 2_000_000; /// Save limit: refuse to save content larger than 50 MB.
const FILE_EDIT_DEFAULT_MAX_BYTES: usize = 400_000; const FILE_SAVE_HARD_LIMIT: usize = 50_000_000;
const FILE_EDIT_MAX_BYTES: usize = 2_000_000;
const FILE_BASE64_DEFAULT_MAX_BYTES: usize = 20_000_000; const FILE_BASE64_DEFAULT_MAX_BYTES: usize = 20_000_000;
const FILE_BASE64_MAX_BYTES: usize = 100_000_000; const FILE_BASE64_MAX_BYTES: usize = 100_000_000;
const FILE_IO_MAX_CONCURRENT_OPS: usize = 8; const FILE_IO_MAX_CONCURRENT_OPS: usize = 8;
@@ -2431,14 +2428,20 @@ fn ensure_path_in_workspace(root: &Path, target: &Path) -> Result<(), AppCommand
Ok(()) Ok(())
} }
fn read_text_preview(target: &Path, limit: usize) -> Result<(String, bool), AppCommandError> { fn read_text_full(target: &Path, hard_limit: usize) -> Result<String, AppCommandError> {
let metadata = std::fs::metadata(target).map_err(AppCommandError::io)?; let metadata = std::fs::metadata(target).map_err(AppCommandError::io)?;
let mut file = File::open(target).map_err(AppCommandError::io)?; if metadata.len() > hard_limit as u64 {
let mut bytes = Vec::new(); return Err(
let mut limited_reader = (&mut file).take(limit as u64 + 1); AppCommandError::invalid_input("File is too large to open in editor")
limited_reader .with_detail(format!(
.read_to_end(&mut bytes) "size={}, limit={}",
.map_err(AppCommandError::io)?; metadata.len(),
hard_limit
)),
);
}
let bytes = std::fs::read(target).map_err(AppCommandError::io)?;
if bytes.iter().take(2_048).any(|b| *b == 0) { if bytes.iter().take(2_048).any(|b| *b == 0) {
return Err(AppCommandError::invalid_input( return Err(AppCommandError::invalid_input(
@@ -2446,11 +2449,7 @@ fn read_text_preview(target: &Path, limit: usize) -> Result<(String, bool), AppC
)); ));
} }
let truncated = bytes.len() > limit || metadata.len() > limit as u64; Ok(String::from_utf8_lossy(&bytes).to_string())
if bytes.len() > limit {
bytes.truncate(limit);
}
Ok((String::from_utf8_lossy(&bytes).to_string(), truncated))
} }
fn atomic_write_text(path: &Path, bytes: &[u8]) -> Result<(), AppCommandError> { fn atomic_write_text(path: &Path, bytes: &[u8]) -> Result<(), AppCommandError> {
@@ -2726,7 +2725,7 @@ pub async fn read_file_base64(
let limit = max_bytes let limit = max_bytes
.unwrap_or(FILE_BASE64_DEFAULT_MAX_BYTES) .unwrap_or(FILE_BASE64_DEFAULT_MAX_BYTES)
.clamp(FILE_PREVIEW_MIN_BYTES, FILE_BASE64_MAX_BYTES); .clamp(4_096, FILE_BASE64_MAX_BYTES);
run_file_io(move || { run_file_io(move || {
let metadata = std::fs::metadata(&target).map_err(AppCommandError::io)?; let metadata = std::fs::metadata(&target).map_err(AppCommandError::io)?;
@@ -2752,7 +2751,6 @@ pub async fn read_file_base64(
pub async fn read_file_preview( pub async fn read_file_preview(
root_path: String, root_path: String,
path: String, path: String,
max_bytes: Option<usize>,
) -> Result<FilePreviewContent, AppCommandError> { ) -> Result<FilePreviewContent, AppCommandError> {
let root = PathBuf::from(&root_path); let root = PathBuf::from(&root_path);
if !root.exists() || !root.is_dir() { if !root.exists() || !root.is_dir() {
@@ -2766,18 +2764,14 @@ pub async fn read_file_preview(
if !target.is_file() { if !target.is_file() {
return Err(AppCommandError::invalid_input("Path is not a file")); return Err(AppCommandError::invalid_input("Path is not a file"));
} }
let limit = max_bytes
.unwrap_or(FILE_PREVIEW_DEFAULT_MAX_BYTES)
.clamp(FILE_PREVIEW_MIN_BYTES, FILE_PREVIEW_MAX_BYTES);
let path_for_response = path.clone(); let path_for_response = path.clone();
run_file_io(move || { run_file_io(move || {
ensure_path_in_workspace(&root, &target)?; ensure_path_in_workspace(&root, &target)?;
let (content, truncated) = read_text_preview(&target, limit)?; let content = read_text_full(&target, FILE_OPEN_HARD_LIMIT)?;
Ok(FilePreviewContent { Ok(FilePreviewContent {
path: path_for_response, path: path_for_response,
content, content,
truncated,
}) })
}) })
.await .await
@@ -2787,7 +2781,6 @@ pub async fn read_file_preview(
pub async fn read_file_for_edit( pub async fn read_file_for_edit(
root_path: String, root_path: String,
path: String, path: String,
max_bytes: Option<usize>,
) -> Result<FileEditContent, AppCommandError> { ) -> Result<FileEditContent, AppCommandError> {
let root = PathBuf::from(&root_path); let root = PathBuf::from(&root_path);
if !root.exists() || !root.is_dir() { if !root.exists() || !root.is_dir() {
@@ -2802,23 +2795,15 @@ pub async fn read_file_for_edit(
return Err(AppCommandError::invalid_input("Path is not a file")); return Err(AppCommandError::invalid_input("Path is not a file"));
} }
let limit = max_bytes
.unwrap_or(FILE_EDIT_DEFAULT_MAX_BYTES)
.clamp(FILE_PREVIEW_MIN_BYTES, FILE_EDIT_MAX_BYTES);
let path_for_response = path.clone(); let path_for_response = path.clone();
run_file_io(move || { run_file_io(move || {
ensure_path_in_workspace(&root, &target)?; ensure_path_in_workspace(&root, &target)?;
let metadata = std::fs::metadata(&target).map_err(AppCommandError::io)?; let metadata = std::fs::metadata(&target).map_err(AppCommandError::io)?;
let (content, truncated) = read_text_preview(&target, limit)?; let content = read_text_full(&target, FILE_OPEN_HARD_LIMIT)?;
let readonly = metadata.permissions().readonly() || truncated; let readonly = metadata.permissions().readonly();
let mtime_ms = file_mtime_ms(&metadata); let mtime_ms = file_mtime_ms(&metadata);
let etag_source = if truncated { let etag = compute_etag(content.as_bytes(), &metadata);
format!("{}:{}", metadata.len(), mtime_ms.unwrap_or_default()).into_bytes()
} else {
content.as_bytes().to_vec()
};
let etag = compute_etag(&etag_source, &metadata);
let line_ending = detect_line_ending(content.as_bytes()); let line_ending = detect_line_ending(content.as_bytes());
Ok(FileEditContent { Ok(FileEditContent {
@@ -2827,7 +2812,6 @@ pub async fn read_file_for_edit(
etag, etag,
mtime_ms, mtime_ms,
readonly, readonly,
truncated,
line_ending, line_ending,
}) })
}) })
@@ -2845,10 +2829,10 @@ pub async fn save_file_content(
if !root.exists() || !root.is_dir() { if !root.exists() || !root.is_dir() {
return Err(AppCommandError::not_found("Folder does not exist")); return Err(AppCommandError::not_found("Folder does not exist"));
} }
if content.len() > FILE_EDIT_MAX_BYTES { if content.len() > FILE_SAVE_HARD_LIMIT {
return Err( return Err(
AppCommandError::invalid_input("File is too large to save in editor") AppCommandError::invalid_input("File is too large to save in editor")
.with_detail(format!("max_bytes={FILE_EDIT_MAX_BYTES}")), .with_detail(format!("max_bytes={FILE_SAVE_HARD_LIMIT}")),
); );
} }
@@ -2944,10 +2928,10 @@ pub async fn save_file_copy(
if !root.exists() || !root.is_dir() { if !root.exists() || !root.is_dir() {
return Err(AppCommandError::not_found("Folder does not exist")); return Err(AppCommandError::not_found("Folder does not exist"));
} }
if content.len() > FILE_EDIT_MAX_BYTES { if content.len() > FILE_SAVE_HARD_LIMIT {
return Err( return Err(
AppCommandError::invalid_input("File is too large to save in editor") AppCommandError::invalid_input("File is too large to save in editor")
.with_detail(format!("max_bytes={FILE_EDIT_MAX_BYTES}")), .with_detail(format!("max_bytes={FILE_SAVE_HARD_LIMIT}")),
); );
} }