修复警告
This commit is contained in:
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user