Skip to content

feat(logs): 日志导出包含 Screencap 截图 - #421

Open
MistEO wants to merge 5 commits into
mainfrom
codex/issue-391
Open

MistEO wants to merge 5 commits into
mainfrom
codex/issue-391

Conversation

@MistEO

@MistEO MistEO commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

执行“导出日志”时,将 debug/screencap 中的 PNG/JPG/JPEG 一并打包,递归保留截图子目录。截图组按修改时间新到旧排序,追加在既有 regular → on_error → vision 之后,保留原导出优先级。

归档名称按原生路径组件构造,对字面反斜杠、百分号、冒号及非 UTF-8 名称作可逆编码,避免 a\\b.png 与 a/b.png 等不同源文件发生 ZIP 名称碰撞。遍历跳过静态文件/目录符号链接和特殊文件。

继续使用原来的 24,500,000 字节常规分卷预算、单个超大文件独占一卷和最近 10 次导出保留规则;文本脱敏流程不变。日志清理范围仍只有原有的 on_error、vision 调试产物,不增加截图清理,不涉及更新、回滚或停止重启。

验证与长期回归:

  • 12 项 Rust 日志导出测试通过,测试已随代码保存在 src-tauri/src/commands/file_ops.rs 的 tests 模块,直接调用生产 export_logs_from_dir 并读取 ZIP 校验路径、条目数量和内容,不依赖临时 harness。
  • 维护者可复跑:cargo test --manifest-path src-tauri/Cargo.toml --locked --lib commands::file_ops::tests。覆盖缺失目录、截图独立导出、嵌套/Unicode 路径、反斜杠及非 UTF-8 名称碰撞、101 张截图、分卷和超大文件、保留/清理范围、文本脱敏、链接/特殊文件、不可读及删除文件。
  • cargo check --manifest-path src-tauri/Cargo.toml --tests --locked 通过。
  • pnpm build 通过;有既有 chunk 大小和动态/静态 import 提示。
  • cargo build --manifest-path src-tauri/Cargo.toml --locked --bin mxu 通过,实际 Linux 应用 mxu --help 退出码 0。
  • 修改文件的 Rustfmt 与 git diff --check 通过。

验证边界:

  • 不可读的调试子目录仍可能在既有 log/json/dmp 收集阶段使导出返回 IO 错误;已增加真实 exporter 回归记录该既有行为,未将其描述为容错通过。
  • 图片按原有二进制导出方式保留原始像素,文本脱敏不处理图像内容。
  • 本地验证为 Linux;未做 Windows/macOS 原生构建、GUI 点击导出或并发替换文件的竞态验证。远程 CI 状态以本 PR 当前 head 的检查为准。

Closes #391.

AI 使用信息:

  • Agent:Codex(云端实现与修复)
  • 模型:gpt-6.1-sol
  • 思考强度:max
  • Agent:Codex(独立审查 review_mxu_391_sol,含 privacy_paths 交叉审查)
  • 模型:gpt-6.1-sol
  • 思考强度:ultra

Sourcery 摘要

扩展日志导出功能,将安全归档的屏幕截图纳入导出内容,同时不改变现有的导出优先级或生命周期规则。

新功能:

  • 将 debug/screencap 目录(包括其嵌套目录)中的 PNG、JPG 和 JPEG 文件纳入导出的日志归档。

错误修复:

  • 通过使用可逆编码保留原生路径组件,避免截图归档路径冲突和不安全的路径处理。
  • 收集截图时跳过符号链接和特殊文件,避免导出非预期数据。

改进:

  • 在添加截图的同时,保留现有的导出顺序、容量限制、超大文件处理方式、保留规则、清理范围和文本脱敏行为。
  • 全面增加回归测试,覆盖截图发现、排序、路径编码、容量拆分、保留、清理和文件系统边缘情况。

测试:

  • 增加覆盖截图导出及现有行为边界的生产路径 Rust 测试。
Original summary in English

Summary by Sourcery

Extend log export to include safely archived screencap images without changing existing export priorities or lifecycle rules.

New Features:

  • Include PNG, JPG, and JPEG files from debug/screencap, including nested directories, in exported log archives.

Bug Fixes:

  • Prevent screenshot archive path collisions and unsafe path handling by preserving native path components with reversible encoding.
  • Skip symbolic links and special files during screenshot collection to avoid exporting unintended data.

Enhancements:

  • Preserve existing export ordering, volume limits, oversized-file handling, retention rules, cleanup scope, and text redaction behavior while adding screenshots.
  • Add comprehensive regression coverage for screenshot discovery, ordering, path encoding, volume splitting, retention, cleanup, and filesystem edge cases.

Tests:

  • Add production-path Rust tests covering screenshot exports and existing behavior boundaries.

Copilot AI balanced review requested due to automatic review settings November 4, 2026 10:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T02:29:21.303710Z 5380f89 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

嘿——我发现了 5 个问题

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src-tauri/src/commands/file_ops.rs" line_range="669-670" />
<code_context>
+    // Screencap 的自定义 filename 可包含子目录;只收集其目录内的 PNG / JPEG,
+    // 保留相对路径,避免不同子目录中的同名截图互相覆盖。
+    let mut screencap_images =
+        collect_files_recursively(&debug_dir.join("screencap"), "screencap")?;
+    screencap_images.retain(|entry| is_image_file(&entry.source_path));
+    screencap_images.sort_by_cached_key(|entry| {
+        std::cmp::Reverse(entry.source_path.metadata().and_then(|m| m.modified()).ok())
</code_context>
<issue_to_address>
**Export contains traversal entries**

When a task or resource author supplies a Screencap filename containing backslash-separated `..` components on Unix, where backslashes are valid filename characters, `collect_files_recursively` passes the relative filename through `normalize_archive_path`, which turns backslashes into `/` without rejecting `..`; `add_file_to_zip` then writes that name directly. An unsafe extractor can write the screenshot outside its extraction directory.

Reject parent-directory components in archive names after normalization, or construct the ZIP name from validated path components that cannot contain separators or `..`.
</issue_to_address>

### Comment 2
<location path="src-tauri/src/commands/file_ops.rs" line_range="669" />
<code_context>
+    // Screencap 的自定义 filename 可包含子目录;只收集其目录内的 PNG / JPEG,
+    // 保留相对路径,避免不同子目录中的同名截图互相覆盖。
+    let mut screencap_images =
+        collect_files_recursively(&debug_dir.join("screencap"), "screencap")?;
+    screencap_images.retain(|entry| is_image_file(&entry.source_path));
+    screencap_images.sort_by_cached_key(|entry| {
</code_context>
<issue_to_address>
**External screenshots leak or exports hang**

When a file or directory symlink exists under `debug/screencap`, `collect_files_recursively` follows symlinks, so external images enter the archive and a link back to an ancestor makes traversal repeat indefinitely; users receive private outside images or an export that never completes.

Make `collect_files_recursively` skip symlinks instead of following them.
</issue_to_address>

### Comment 3
<location path="src-tauri/src/commands/file_ops.rs" line_range="669" />
<code_context>
+    // Screencap 的自定义 filename 可包含子目录;只收集其目录内的 PNG / JPEG,
+    // 保留相对路径,避免不同子目录中的同名截图互相覆盖。
+    let mut screencap_images =
+        collect_files_recursively(&debug_dir.join("screencap"), "screencap")?;
+    screencap_images.retain(|entry| is_image_file(&entry.source_path));
+    screencap_images.sort_by_cached_key(|entry| {
</code_context>
<issue_to_address>
**Unreadable folders abort exports**

When a `debug/screencap` directory or nested directory cannot be read, `collect_files_recursively` propagates the `read_dir` error through `?`, so `export_logs_blocking` returns without producing a ZIP even when other logs are readable, leaving the created export directory behind.

Handle Screencap traversal errors locally and continue collecting readable entries instead of propagating them out of `export_logs_blocking`.
</issue_to_address>

### Comment 4
<location path="src-tauri/src/commands/file_ops.rs" line_range="681" />
<code_context>
     all_entries.extend(regular_entries);
     all_entries.extend(on_error_images);
     all_entries.extend(vision_images);
+    all_entries.extend(screencap_images);

     let total_entries = all_entries.len();
</code_context>
<issue_to_address>
**Cleared screenshots reappear in exports**

When the user clears logs manually or through auto-clear, then exports logs again, `clear_log_dirs` and `clear_log_files` leave `debug/screencap` intact, while `export_logs_blocking` includes its images, so users see old screenshots in a later export.

Clear the `debug/screencap` images as part of the log-clearing paths.
</issue_to_address>

### Comment 5
<location path="src-tauri/src/commands/file_ops.rs" line_range="681" />
<code_context>
     all_entries.extend(regular_entries);
     all_entries.extend(on_error_images);
     all_entries.extend(vision_images);
+    all_entries.extend(screencap_images);

     let total_entries = all_entries.len();
</code_context>
<issue_to_address>
**Newest screenshots land in later volumes**

When older `on_error` or `vision` images precede a newer Screencap image and the export spans multiple volumes, `export_logs_blocking` appends `screencap_images` after the other image groups, and the volume writer consumes that order, so older images can fill an earlier volume before the newer screenshot is reached.

Order the image entries together by modification time before passing them to the volume writer.
</issue_to_address>

关联问题

#391 [feat] 节点动作类型Screencap的截图跟随日志导出 · 1 项要求中已满足 1 项

# 要求 状态 备注
1 将 debug/screencap 中 Screencap 动作保存的截图随日志导出打包。 ✅ 已满足 递归收集 screencap 目录内的 PNG/JPG/JPEG,并以 screencap/ 下的相对路径写入现有导出条目。

Sourcery 为开源项目免费提供服务——如果你喜欢我们的评审,请考虑分享 ✨
Original comment in English

Hey - I've found 5 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src-tauri/src/commands/file_ops.rs" line_range="669-670" />
<code_context>
+    // Screencap 的自定义 filename 可包含子目录;只收集其目录内的 PNG / JPEG,
+    // 保留相对路径,避免不同子目录中的同名截图互相覆盖。
+    let mut screencap_images =
+        collect_files_recursively(&debug_dir.join("screencap"), "screencap")?;
+    screencap_images.retain(|entry| is_image_file(&entry.source_path));
+    screencap_images.sort_by_cached_key(|entry| {
+        std::cmp::Reverse(entry.source_path.metadata().and_then(|m| m.modified()).ok())
</code_context>
<issue_to_address>
**Export contains traversal entries**

When a task or resource author supplies a Screencap filename containing backslash-separated `..` components on Unix, where backslashes are valid filename characters, `collect_files_recursively` passes the relative filename through `normalize_archive_path`, which turns backslashes into `/` without rejecting `..`; `add_file_to_zip` then writes that name directly. An unsafe extractor can write the screenshot outside its extraction directory.

Reject parent-directory components in archive names after normalization, or construct the ZIP name from validated path components that cannot contain separators or `..`.
</issue_to_address>

### Comment 2
<location path="src-tauri/src/commands/file_ops.rs" line_range="669" />
<code_context>
+    // Screencap 的自定义 filename 可包含子目录;只收集其目录内的 PNG / JPEG,
+    // 保留相对路径,避免不同子目录中的同名截图互相覆盖。
+    let mut screencap_images =
+        collect_files_recursively(&debug_dir.join("screencap"), "screencap")?;
+    screencap_images.retain(|entry| is_image_file(&entry.source_path));
+    screencap_images.sort_by_cached_key(|entry| {
</code_context>
<issue_to_address>
**External screenshots leak or exports hang**

When a file or directory symlink exists under `debug/screencap`, `collect_files_recursively` follows symlinks, so external images enter the archive and a link back to an ancestor makes traversal repeat indefinitely; users receive private outside images or an export that never completes.

Make `collect_files_recursively` skip symlinks instead of following them.
</issue_to_address>

### Comment 3
<location path="src-tauri/src/commands/file_ops.rs" line_range="669" />
<code_context>
+    // Screencap 的自定义 filename 可包含子目录;只收集其目录内的 PNG / JPEG,
+    // 保留相对路径,避免不同子目录中的同名截图互相覆盖。
+    let mut screencap_images =
+        collect_files_recursively(&debug_dir.join("screencap"), "screencap")?;
+    screencap_images.retain(|entry| is_image_file(&entry.source_path));
+    screencap_images.sort_by_cached_key(|entry| {
</code_context>
<issue_to_address>
**Unreadable folders abort exports**

When a `debug/screencap` directory or nested directory cannot be read, `collect_files_recursively` propagates the `read_dir` error through `?`, so `export_logs_blocking` returns without producing a ZIP even when other logs are readable, leaving the created export directory behind.

Handle Screencap traversal errors locally and continue collecting readable entries instead of propagating them out of `export_logs_blocking`.
</issue_to_address>

### Comment 4
<location path="src-tauri/src/commands/file_ops.rs" line_range="681" />
<code_context>
     all_entries.extend(regular_entries);
     all_entries.extend(on_error_images);
     all_entries.extend(vision_images);
+    all_entries.extend(screencap_images);

     let total_entries = all_entries.len();
</code_context>
<issue_to_address>
**Cleared screenshots reappear in exports**

When the user clears logs manually or through auto-clear, then exports logs again, `clear_log_dirs` and `clear_log_files` leave `debug/screencap` intact, while `export_logs_blocking` includes its images, so users see old screenshots in a later export.

Clear the `debug/screencap` images as part of the log-clearing paths.
</issue_to_address>

### Comment 5
<location path="src-tauri/src/commands/file_ops.rs" line_range="681" />
<code_context>
     all_entries.extend(regular_entries);
     all_entries.extend(on_error_images);
     all_entries.extend(vision_images);
+    all_entries.extend(screencap_images);

     let total_entries = all_entries.len();
</code_context>
<issue_to_address>
**Newest screenshots land in later volumes**

When older `on_error` or `vision` images precede a newer Screencap image and the export spans multiple volumes, `export_logs_blocking` appends `screencap_images` after the other image groups, and the volume writer consumes that order, so older images can fill an earlier volume before the newer screenshot is reached.

Order the image entries together by modification time before passing them to the volume writer.
</issue_to_address>

Linked issues

#391 [feat] 节点动作类型Screencap的截图跟随日志导出 · 1 of 1 met

# Requirement Status Notes
1 将 debug/screencap 中 Screencap 动作保存的截图随日志导出打包。 ✅ Met 递归收集 screencap 目录内的 PNG/JPG/JPEG,并以 screencap/ 下的相对路径写入现有导出条目。

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src-tauri/src/commands/file_ops.rs Resolved
Comment thread src-tauri/src/commands/file_ops.rs Resolved
Comment thread src-tauri/src/commands/file_ops.rs Resolved
Comment thread src-tauri/src/commands/file_ops.rs Resolved
Comment thread src-tauri/src/commands/file_ops.rs Resolved
@MistEO MistEO changed the title feat(logs): 导出日志时包含 Screencap 截图 feat(logs): 安全导出并清理 Screencap 调试截图 Nov 4, 2026
@MistEO MistEO closed this Nov 4, 2026
@MistEO MistEO changed the title feat(logs): 安全导出并清理 Screencap 调试截图 feat(logs): 日志导出包含 Screencap 截图 Nov 11, 2026
@MistEO MistEO reopened this Nov 11, 2026
@MistEO
MistEO marked this pull request as draft November 11, 2026 01:54
@MistEO
MistEO marked this pull request as ready for review November 11, 2026 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 节点动作类型Screencap的截图跟随日志导出

2 participants