归档当前功能基线并完善工作区 Agent
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+78
-1188
File diff suppressed because it is too large
Load Diff
@@ -109,6 +109,8 @@ Qt 版本:
|
||||
|
||||
## 本地文件操作
|
||||
|
||||
当前源码仍保留安全受限的本地文件操作 v1。后续将由本地工作区 Agent 替代;替代完成前,以下用例仍作为回归项保留。
|
||||
|
||||
| 用例 | 步骤 | 预期结果 | 结论 |
|
||||
| --- | --- | --- | --- |
|
||||
| 读取文本 | 输入读取文本文件请求并选择 txt/md/log | 显示内容预览,最多读取前 64KB | TODO |
|
||||
@@ -122,6 +124,56 @@ Qt 版本:
|
||||
| 符号链接拒绝 | 选择符号链接或链接目录内路径 | 提示不允许操作符号链接路径 | TODO |
|
||||
| zip 延期 | 输入打包/压缩请求 | 提示 zip 打包暂不启用 | TODO |
|
||||
|
||||
## 本地工作区 Agent
|
||||
|
||||
v1a.1/v1b/v1b.5 已接入工作区只读分析、打开定位和独立 Agent 窗口;创建、修改、移动、删除到回收站仍属后续阶段。正式施工计划见 `docs/agent/DesktopPet_本地工作区Agent_施工计划.md`。
|
||||
|
||||
| 用例 | 步骤 | 预期结果 | 结论 |
|
||||
| --- | --- | --- | --- |
|
||||
| 选择工作区 | 选择普通项目目录 | 显示当前工作区并扫描索引 | TODO |
|
||||
| 系统目录拒绝 | 选择 `C:/Windows` 或 `Program Files` | 拒绝作为工作区 | TODO |
|
||||
| 扫描跳过目录 | 工作区含 `.git/build/node_modules/release_packages` | 扫描跳过这些目录,结果数量受限制 | TODO |
|
||||
| 搜索文件名 | 输入 `搜索 README` | 返回最多 30 个相对路径结果 | TODO |
|
||||
| 独立 Agent 窗口 | 输入 `搜索 README` | 独立窗口展示上一轮结果,可选中结果;左侧结果/文本命中/上下文、中间对话、右侧详情分区清晰 | TODO |
|
||||
| 窗口非客户区 | 打开独立 Agent 窗口 | 没有白色系统标题栏,标题区与整体深色 UI 协调,顶部可拖动移动窗口 | TODO |
|
||||
| 搜索文本 | 输入 `搜索包含 class 的文件` | 返回路径、行号和片段,跳过二进制和敏感文件 | TODO |
|
||||
| 紧凑文本搜索 | 输入 `含class的文件`、`包含 class 的文件`、`找一下含 class 的文件` | 走 Workspace 文本搜索,不弹旧文本文件选择框,关键词提取为 `class` | TODO |
|
||||
| 文本命中窗口 | 输入 `搜索包含 class 的文件` | 独立窗口展示文本命中和关联文件列表,选中文本命中时右侧详情同步 | TODO |
|
||||
| 读取文本文件 | 输入 `读取 main.cpp` | 读取工作区内文本文件,超大文件截断 | TODO |
|
||||
| 窗口读取 | 搜索后在独立窗口选中结果并点击 `读取` | 读取选中文本文件,目录项读取按钮禁用 | TODO |
|
||||
| 敏感文件拒绝 | 读取 `.env`、`*.pem`、`credentials.json` | 默认拒绝或强确认,不展示敏感内容 | TODO |
|
||||
| 项目结构摘要 | 输入 `分析这个项目` | 显示本地结构摘要;AI 可用且空闲时补简短分析 | TODO |
|
||||
| 文件总结 | 读取文件后输入 `总结这个文件` | 显示本地内容,AI 可用且空闲时补简短总结 | TODO |
|
||||
| 文件分析同义词 | 读取 `main.cpp` 后输入 `分析这个文件`、`解释这个文件`、`看看这个文件` | 走 Workspace 最近读取文件分析,不弹旧文本文件选择框 | TODO |
|
||||
| 文件名分析 | 输入 `分析 main.cpp`、`解释 README`、`main.cpp 是干嘛的` | 走 Workspace 文件读取和 AI 分析流程,不落入普通 AI 对话 | TODO |
|
||||
| 总结无上下文 | 未读取工作区文件时输入 `总结这个文件` | 提示先读取一个工作区文件或输入具体文件名,不弹旧文件选择框 | TODO |
|
||||
| 读取文件泛请求 | 输入 `读取文件` | 仍走旧 fileops 文本文件选择流程 | TODO |
|
||||
| 上下文引用 | 先搜索 README,再输入 `读取第一个` | 正确引用上一轮结果;上下文过期则要求重新选择 | TODO |
|
||||
| 打开文件 | 先搜索 README,再输入 `打开第一个` | 弹出确认框,确认后用系统默认程序打开;取消不打开 | TODO |
|
||||
| 窗口打开 | 搜索后在独立窗口选中结果并点击 `打开` | 文件弹确认后打开;目录直接打开 | TODO |
|
||||
| 打开目录 | 输入 `打开 docs` 或 `打开这个项目里的 docs` | 打开工作区内目录 | TODO |
|
||||
| 显示位置 | 输入 `显示 main.cpp 所在位置` 或 `打开所在文件夹` | 打开工作区内文件所在目录 | TODO |
|
||||
| 窗口显示位置 | 搜索后在独立窗口选中结果并点击 `位置` | 打开选中项所在目录 | TODO |
|
||||
| 右键入口 | 右键桌宠选择 `Agent 工作区` | 独立 Agent 窗口打开;未选择工作区时显示空状态,不强制选择目录 | TODO |
|
||||
| 托盘入口 | 托盘菜单选择 `Agent 工作区` | 独立 Agent 窗口打开;桌宠隐藏时也可直接打开窗口 | TODO |
|
||||
| 默认快捷键 | 按 `Alt+A` | 呼出独立 Agent 窗口 | TODO |
|
||||
| Agent 设置 | 设置页 `Agent` 页关闭快捷键或修改快捷键后保存 | 快捷键立即按新配置生效,非法/冲突快捷键有明确提示 | TODO |
|
||||
| 自动显示设置 | 关闭 `工作区命令完成后自动显示 Agent 窗口` 后输入 `搜索 README` | 搜索正常完成,但独立窗口不自动弹出;手动入口仍可打开 | TODO |
|
||||
| 打开敏感/危险文件拒绝 | 尝试打开 `.exe/.ps1/.lnk/.env/*.pem/credentials.json` | 明确拒绝,不调用系统打开 | TODO |
|
||||
| 打开路径越界拒绝 | 尝试打开工作区外、系统目录、符号链接路径 | 明确拒绝 | TODO |
|
||||
| 创建误分发修复 | 输入 `创建 test.txt` | 提示当前不支持创建/写入,不弹读取文本文件选择框 | TODO |
|
||||
| 写入误分发修复 | 输入 `新建文件`、`写入 README`、`修改 main.cpp`、`修复 main.cpp`、`优化 main.cpp` | 明确拒绝当前阶段写操作,不弹读取文本文件选择框 | TODO |
|
||||
| 危险能力拒绝 | 请求运行脚本、执行命令、自动安装依赖、自动 git commit | 明确拒绝,不调用系统命令 | TODO |
|
||||
|
||||
后续阶段验收项:
|
||||
|
||||
| 用例 | 步骤 | 预期结果 | 结论 |
|
||||
| --- | --- | --- | --- |
|
||||
| 创建文件 | 创建 `test.txt` | 展示计划,确认后创建;目标存在拒绝 | TODO |
|
||||
| 修改文件 | 修改普通文本文件 | 展示计划,确认后写入;写入前生成备份 | TODO |
|
||||
| 移动/重命名 | 移动或重命名单文件 | 展示计划,确认后执行;工作区外和覆盖目标拒绝 | TODO |
|
||||
| 删除到回收站 | 删除普通文件 | 强确认后移动到回收站;永久删除和批量删除拒绝 | TODO |
|
||||
|
||||
## 联网模式
|
||||
|
||||
| 用例 | 步骤 | 预期结果 | 结论 |
|
||||
@@ -193,6 +245,7 @@ UI 状态:
|
||||
| 角色 | 设置页角色页和 `CharacterPackageRepository` | 导入先验证再复制;切换保存后立即生效;用户角色可删除;任意角色可导出;可打开用户角色目录 | 内置角色不可删除/覆盖;导出目标存在需二次确认;损坏角色包不复制 | 目标目录权限失败、用户取消、角色包资源缺失 | 当前角色管理补全完成,角色市场/分享延期 |
|
||||
| 历史 | 设置页聊天页和 `ConversationManager/ConversationStore` | 新消息写入 `timestamp/provider/model`;旧 role/content 历史兼容读取;支持关键词、Provider、模型筛选;Markdown/JSON 导出 | 本地历史关闭时只显示内存记录;损坏历史备份后忽略;导出路径不可写时提示错误 | 超大历史由保存上限和筛选 UI 控制;导出文件由用户选择路径 | 历史管理补全完成,不导出 API Key,不记录完整聊天日志 |
|
||||
| 文件操作 | `CommandDispatcher -> PetWindow::handleFileOperationChatMessage() -> FileOperationManager` | 用户通过系统对话框选择路径;读取文本、列目录、复制、备份、重命名;写操作先展示计划并二次确认 | 删除/覆盖/移动/脚本/命令/截图保存/zip 直接拒绝;系统目录、符号链接、目标冲突、非法文件名均拒绝 | 文件选择对话框是安全边界;系统目录根据常见路径和环境变量识别 | v1 安全能力完成,zip 打包延期,不引入重依赖 |
|
||||
| 联网模式 | `ChatInputDialog -> PetWindow::submitWebChatMessage() -> WebChatManager` | 输入框联网开关开启后,支持 OpenAI 官方 Web Search 和 Gemini Google Search grounding;不支持 DeepSeek/Custom 伪联网 | AI 未配置、Provider 不支持、超时、网络失败、无来源均明确提示;Web 忙时不启动第二个请求 | 不再维护旧搜索源聚合或 SearXNG 旧配置 | 联网模式完成,结构化搜索 API/自建后端延期 |
|
||||
| 本地工作区 Agent | `CommandDispatcher -> PetWindow::handleWorkspaceChatMessage() -> WorkspaceController -> WorkspaceAgent/WorkspaceTool -> WorkspaceAgentWindow` | v1b.5 用户选择工作区后,扫描索引、搜索文件/文本、读取文本文件、生成本地摘要,AI 可用时补简短分析,并支持打开普通文件、打开文件夹、显示所在位置、独立 Agent 窗口、菜单入口和全局快捷键 | 工作区外路径、系统目录、符号链接、危险后缀、敏感文件、命令执行均拒绝;打开文件前确认;AI 忙时不阻塞本地结果 | 路径归一化、符号链接逃逸、敏感文件判断、意图误判、全局快捷键冲突是核心风险;写操作仍未进入当前阶段 | v1a.1/v1b/v1b.5 独立窗口化已落地;低风险写操作计划留到后续 |
|
||||
| 联网模式 | `ChatInputDialog -> PetWindow::submitWebChatMessage() -> WebChatManager` | 输入框联网开关开启后,支持 OpenAI 官方 Web Search 和 Gemini Google Search grounding;记住开关状态时用户切换会立即保存 | AI 未配置、Provider 不支持、超时、网络失败、无来源均明确提示;Web 忙时不启动第二个请求 | 不再维护旧搜索源聚合或 SearXNG 旧配置 | 联网模式完成,结构化搜索 API/自建后端延期 |
|
||||
| 应用启动 | `CommandDispatcher -> PetWindow::handleLaunchAppChatMessage() -> AppLaunchManager` | 用户输入打开/启动应用后,按登记别名、开始菜单、App Paths、手选 exe 顺序解析,启动前二次确认 | 配置关闭、未找到应用、用户取消、非 exe/lnk、脚本/安装包、路径不存在均不启动;手选记住失败只提示,不影响已确认启动 | 启动成功由系统返回值判断,目标应用自身异常不由桌宠控制;开始菜单和注册表发现仅 Windows 优先 | v1 完成,不支持脚本、命令行参数、管理员权限、跨平台发现 |
|
||||
| 发布/性能 | `tools/package_release.ps1` 和 `docs/performance_stability_check.md` | 发布脚本复制 exe、Qt runtime、角色/图标/音效资源、README、LICENSE;性能文档覆盖静置、AI、提醒、天气、联网模式、文件操作 | `windeployqt`、Inno Setup、安装/卸载验证需用户手动执行并记录 | 未经手动构建和实机安装不能确认二进制运行 | 验收入口已补齐,本轮不运行 CMake 或构建命令 |
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# QtDesktopPet 文档索引
|
||||
|
||||
本文档用于区分当前有效文档和历史归档文档,避免后续开发继续引用过时路线。
|
||||
|
||||
## 当前有效文档
|
||||
|
||||
| 文档 | 用途 |
|
||||
| --- | --- |
|
||||
| `QtDesktopPet_后续功能规划与结构审查.md` | 当前功能状态、下一阶段路线和结构边界 |
|
||||
| `QtDesktopPet_测试清单与验收标准.md` | 功能测试清单、回归验收标准和静态推演记录 |
|
||||
| `performance_stability_check.md` | 性能采样、稳定性检查和发布验证入口 |
|
||||
| `agent/DesktopPet_本地工作区Agent_施工计划.md` | 本地工作区 Agent 的正式施工计划 |
|
||||
|
||||
## 归档文档
|
||||
|
||||
历史文档统一放在 `docs/archive/`。这些文档保留为开发上下文,不再作为当前施工依据。
|
||||
|
||||
| 文档 | 归档原因 |
|
||||
| --- | --- |
|
||||
| `archive/implementation_plan.md` | 早期分阶段实施计划,当前大部分阶段已完成或被后续模块替代 |
|
||||
| `archive/Qt_DesktopPet_开发文档.md` | 初版开发总纲,当前产品能力已超出第一版范围 |
|
||||
| `archive/QtDesktopPet_后续功能规划与结构审查_旧版.md` | 旧后续规划过长,包含大量已完成阶段和旧文件操作路线 |
|
||||
| `archive/DesktopPet 本地工作区 Agent 开发文档_草案.md` | 本地工作区 Agent 初稿,已整理为正式施工计划 |
|
||||
|
||||
## 维护规则
|
||||
|
||||
- 新功能进入开发前,先更新当前路线文档和对应测试清单。
|
||||
- 功能完成后,同步更新 README、测试清单和性能检查项。
|
||||
- 历史文档不再直接修改,除非补充归档说明。
|
||||
- GitHub 公开导出不包含 `docs/`;开发仓库保留完整文档。
|
||||
- 在构建卡住问题解决前,文档中的验证步骤仍以用户手动构建为准,不由 Codex 调用 CMake 或构建命令。
|
||||
@@ -0,0 +1,869 @@
|
||||
# DesktopPet 本地工作区 Agent 施工计划
|
||||
|
||||
## 1. 定位
|
||||
|
||||
本地工作区 Agent 是下一阶段核心功能。它不是继续维护旧版“文件操作 v1”,而是新增一个独立的工作区系统:
|
||||
|
||||
> 用户选择一个本地工作区目录后,桌宠可以读取、搜索、分析该目录内的文件,并在用户确认后创建、修改、复制、移动、重命名或删除到回收站。
|
||||
|
||||
第一阶段优先实现文件级工具,不做 Shell 命令执行,不做脚本执行,不做自动安装依赖。
|
||||
|
||||
## 2. 总原则
|
||||
|
||||
- 不允许 AI 直接获得全盘文件权限。
|
||||
- 不允许 AI 自由执行系统命令。
|
||||
- 不允许永久删除文件。
|
||||
- 不允许绕过用户确认修改本地文件。
|
||||
- 不允许默认读取敏感文件。
|
||||
- 所有写操作必须先生成计划,展示路径、动作和风险,用户确认后才能执行。
|
||||
- 所有文件路径必须限制在用户选择的工作区内。
|
||||
- `PetWindow` 只负责 UI 入口、展示和确认,不承载复杂业务逻辑。
|
||||
- 新模块先新增、后替换旧 `src/fileops/`。
|
||||
|
||||
## 3. 分期目标
|
||||
|
||||
当前执行状态:
|
||||
|
||||
- v1a.1/v1b/v1b.5 已落地到 `src/workspace/` 和 `src/ui/WorkspaceAgentWindow.*`。
|
||||
- 当前不持久化工作区路径,不执行写操作,不替换旧 `src/fileops/`。
|
||||
- v1b 已支持打开工作区内普通文件、打开文件夹、显示文件所在位置。
|
||||
- v1b.5 已提供独立 Agent 工作区窗口、右键/托盘入口和默认 `Alt+A` 呼出快捷键;窗口包含左侧结果导航、中间任务对话、右侧详情/上下文和 v1c 预留变更区。
|
||||
|
||||
### v1a:只读工作区
|
||||
|
||||
目标:
|
||||
|
||||
- 选择工作区。
|
||||
- 显示当前工作区。
|
||||
- 扫描工作区文件树。
|
||||
- 搜索文件名。
|
||||
- 搜索文本内容。
|
||||
- 读取文本文件。
|
||||
- 分析项目结构。
|
||||
- 总结文件内容。
|
||||
|
||||
验收:
|
||||
|
||||
- 工作区外路径被拒绝。
|
||||
- 系统目录被拒绝。
|
||||
- `.git/build/node_modules/dist/release_packages` 等目录被跳过。
|
||||
- 敏感文件默认拒绝读取。
|
||||
- 二进制文件拒绝读取。
|
||||
|
||||
### v1b:打开和定位
|
||||
|
||||
状态:已落地。
|
||||
|
||||
目标:
|
||||
|
||||
- 打开工作区内普通文件。
|
||||
- 打开工作区内文件夹。
|
||||
- 显示文件所在位置。
|
||||
|
||||
验收:
|
||||
|
||||
- 危险后缀拒绝打开。
|
||||
- `.lnk` 不直接打开。
|
||||
- 工作区外路径拒绝。
|
||||
- 打开文件必须二次确认,打开文件夹作为低风险操作直接执行。
|
||||
|
||||
### v1c:低风险写入
|
||||
|
||||
进入 v1c 前已完成:
|
||||
|
||||
- `创建 test.txt / 新建文件 / 写入 README / 修改 main.cpp` 明确拒绝,不再误进入旧 fileops 读取文件流程。
|
||||
- 独立 Agent 窗口可以分区展示上一轮结果、文本命中、上下文、选中项详情和最近读取文件。
|
||||
- 窗口支持对选中结果执行读取、打开和显示位置。
|
||||
- 窗口可通过桌宠右键菜单、托盘菜单和可配置全局快捷键呼出。
|
||||
|
||||
目标:
|
||||
|
||||
- 创建文本文件。
|
||||
- 创建文件夹。
|
||||
- 复制单个文件。
|
||||
- 同目录重命名文件。
|
||||
|
||||
验收:
|
||||
|
||||
- 所有写操作必须确认。
|
||||
- 目标文件或目录已存在时拒绝覆盖。
|
||||
- 文件名非法时拒绝。
|
||||
- 危险后缀拒绝创建或写入。
|
||||
|
||||
### v1d:高风险写入
|
||||
|
||||
目标:
|
||||
|
||||
- 修改已有文本文件。
|
||||
- 移动单个文件。
|
||||
- 删除文件到回收站。
|
||||
- 删除文件夹到回收站。
|
||||
|
||||
验收:
|
||||
|
||||
- 修改前必须备份。
|
||||
- 移动和删除必须强确认。
|
||||
- 删除只进入回收站。
|
||||
- 批量删除第一阶段拒绝。
|
||||
- 失败时不能破坏原文件。
|
||||
|
||||
### v1e:替换旧文件操作
|
||||
|
||||
目标:
|
||||
|
||||
- `FileOperation` 意图迁移为 `Workspace`。
|
||||
- `PetWindow::handleFileOperationChatMessage()` 过渡为调用 `WorkspaceController`。
|
||||
- 新模块覆盖旧能力后删除旧 `src/fileops/`。
|
||||
|
||||
验收:
|
||||
|
||||
- 无 `FileOperationManager` 有效引用。
|
||||
- CMake 不再登记旧 fileops 文件。
|
||||
- README 和测试文档改为“本地工作区 Agent”。
|
||||
|
||||
## 4. 新目录结构
|
||||
|
||||
建议新增:
|
||||
|
||||
```text
|
||||
src/workspace/
|
||||
├── WorkspaceTypes.h
|
||||
├── WorkspaceSession.h
|
||||
├── WorkspaceSession.cpp
|
||||
├── WorkspaceIndex.h
|
||||
├── WorkspaceIndex.cpp
|
||||
├── WorkspaceScanner.h
|
||||
├── WorkspaceScanner.cpp
|
||||
├── WorkspaceAccessPolicy.h
|
||||
├── WorkspaceAccessPolicy.cpp
|
||||
├── WorkspaceTool.h
|
||||
├── WorkspaceToolRegistry.h
|
||||
├── WorkspaceToolRegistry.cpp
|
||||
├── WorkspaceAgent.h
|
||||
├── WorkspaceAgent.cpp
|
||||
├── WorkspaceController.h
|
||||
├── WorkspaceController.cpp
|
||||
├── WorkspaceHistory.h
|
||||
├── WorkspaceHistory.cpp
|
||||
└── tools/
|
||||
├── ListFilesTool.h
|
||||
├── ListFilesTool.cpp
|
||||
├── ReadFileTool.h
|
||||
├── ReadFileTool.cpp
|
||||
├── SearchFilesTool.h
|
||||
├── SearchFilesTool.cpp
|
||||
├── SearchTextTool.h
|
||||
├── SearchTextTool.cpp
|
||||
├── OpenFileTool.h
|
||||
├── OpenFileTool.cpp
|
||||
├── CreateFileTool.h
|
||||
├── CreateFileTool.cpp
|
||||
├── CreateDirectoryTool.h
|
||||
├── CreateDirectoryTool.cpp
|
||||
├── WriteFileTool.h
|
||||
├── WriteFileTool.cpp
|
||||
├── CopyFileTool.h
|
||||
├── CopyFileTool.cpp
|
||||
├── MoveFileTool.h
|
||||
├── MoveFileTool.cpp
|
||||
├── RenameFileTool.h
|
||||
├── RenameFileTool.cpp
|
||||
├── DeleteToTrashTool.h
|
||||
└── DeleteToTrashTool.cpp
|
||||
```
|
||||
|
||||
第一阶段继续保持 `CMAKE_AUTOMOC OFF`,新增业务类不使用 `Q_OBJECT`。
|
||||
|
||||
## 5. 核心类型
|
||||
|
||||
```cpp
|
||||
enum class WorkspacePermissionMode
|
||||
{
|
||||
ReadOnly,
|
||||
ConfirmBeforeWrite,
|
||||
Advanced
|
||||
};
|
||||
```
|
||||
|
||||
第一阶段默认 `ConfirmBeforeWrite`,但所有写操作仍必须确认。`Advanced` 只预留,不启用自动写入。
|
||||
|
||||
```cpp
|
||||
enum class WorkspaceToolRisk
|
||||
{
|
||||
ReadOnly,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Dangerous
|
||||
};
|
||||
```
|
||||
|
||||
风险定义:
|
||||
|
||||
- `ReadOnly`:列目录、搜索、读取普通文本文件。
|
||||
- `Low`:打开文件夹、显示文件所在位置。
|
||||
- `Medium`:打开普通文件、创建文件、创建文件夹、复制文件。
|
||||
- `High`:修改文件、移动文件、重命名文件、删除到回收站。
|
||||
- `Dangerous`:永久删除、执行命令、运行脚本、修改系统目录。
|
||||
|
||||
第一阶段遇到 `Dangerous` 直接拒绝。
|
||||
|
||||
```cpp
|
||||
struct WorkspaceFileInfo
|
||||
{
|
||||
QString relativePath;
|
||||
QString absolutePath;
|
||||
QString fileName;
|
||||
QString suffix;
|
||||
qint64 sizeBytes = 0;
|
||||
QDateTime lastModified;
|
||||
bool isDirectory = false;
|
||||
bool isTextFile = false;
|
||||
bool isHidden = false;
|
||||
};
|
||||
```
|
||||
|
||||
```cpp
|
||||
enum class WorkspaceActionType
|
||||
{
|
||||
Unknown,
|
||||
ListFiles,
|
||||
SearchFiles,
|
||||
SearchText,
|
||||
ReadFile,
|
||||
OpenFile,
|
||||
OpenDirectory,
|
||||
RevealInExplorer,
|
||||
CreateFile,
|
||||
CreateDirectory,
|
||||
WriteFile,
|
||||
CopyFile,
|
||||
MoveFile,
|
||||
RenameFile,
|
||||
DeleteToTrash
|
||||
};
|
||||
```
|
||||
|
||||
```cpp
|
||||
struct WorkspaceActionPlan
|
||||
{
|
||||
QString id;
|
||||
WorkspaceActionType type = WorkspaceActionType::Unknown;
|
||||
WorkspaceToolRisk risk = WorkspaceToolRisk::ReadOnly;
|
||||
QString title;
|
||||
QString description;
|
||||
QString sourcePath;
|
||||
QString targetPath;
|
||||
QString relativeSourcePath;
|
||||
QString relativeTargetPath;
|
||||
QString content;
|
||||
QString searchKeyword;
|
||||
QStringList warnings;
|
||||
bool requiresConfirmation = false;
|
||||
bool allowOverwrite = false;
|
||||
bool confirmed = false;
|
||||
};
|
||||
```
|
||||
|
||||
```cpp
|
||||
struct WorkspaceToolResult
|
||||
{
|
||||
bool success = false;
|
||||
QString message;
|
||||
QString errorMessage;
|
||||
QString outputText;
|
||||
QString targetPath;
|
||||
QVector<WorkspaceFileInfo> files;
|
||||
};
|
||||
```
|
||||
|
||||
## 6. 安全策略
|
||||
|
||||
`WorkspaceAccessPolicy` 是最重要的模块,所有工具必须经过它校验。
|
||||
|
||||
职责:
|
||||
|
||||
- 路径标准化。
|
||||
- 判断路径是否在工作区内。
|
||||
- 阻止 `../` 跳出工作区。
|
||||
- 阻止符号链接跳出工作区。
|
||||
- 阻止系统目录。
|
||||
- 阻止危险文件类型。
|
||||
- 阻止敏感文件默认读取。
|
||||
- 限制写入行为。
|
||||
- 限制删除行为。
|
||||
|
||||
建议接口:
|
||||
|
||||
```cpp
|
||||
class WorkspaceAccessPolicy
|
||||
{
|
||||
public:
|
||||
explicit WorkspaceAccessPolicy(const QString &workspaceRoot);
|
||||
|
||||
QString workspaceRoot() const;
|
||||
QString normalizePath(const QString &path) const;
|
||||
QString toAbsolutePath(const QString &relativeOrAbsolutePath) const;
|
||||
QString toRelativePath(const QString &absolutePath) const;
|
||||
|
||||
bool isInsideWorkspace(const QString &path, QString *errorMessage = nullptr) const;
|
||||
bool hasSymlinkSegment(const QString &path, QString *errorMessage = nullptr) const;
|
||||
bool isSystemPath(const QString &path) const;
|
||||
bool isDangerousSuffix(const QString &path) const;
|
||||
bool isSensitiveFile(const QString &path) const;
|
||||
bool isSupportedTextFile(const QString &path) const;
|
||||
|
||||
bool canReadFile(const QString &path, QString *errorMessage = nullptr) const;
|
||||
bool canReadDirectory(const QString &path, QString *errorMessage = nullptr) const;
|
||||
bool canCreateFile(const QString &path, QString *errorMessage = nullptr) const;
|
||||
bool canCreateDirectory(const QString &path, QString *errorMessage = nullptr) const;
|
||||
bool canWriteFile(const QString &path, QString *errorMessage = nullptr) const;
|
||||
bool canMoveFile(const QString &sourcePath, const QString &targetPath, QString *errorMessage = nullptr) const;
|
||||
bool canDeleteToTrash(const QString &path, QString *errorMessage = nullptr) const;
|
||||
};
|
||||
```
|
||||
|
||||
禁止危险后缀:
|
||||
|
||||
```text
|
||||
.exe .bat .cmd .ps1 .vbs .msi .scr .reg .com .pif .lnk
|
||||
```
|
||||
|
||||
敏感文件默认拒绝:
|
||||
|
||||
```text
|
||||
.env
|
||||
*.pem
|
||||
*.key
|
||||
id_rsa
|
||||
id_ed25519
|
||||
*.pfx
|
||||
*.p12
|
||||
credentials.json
|
||||
secrets.json
|
||||
token.json
|
||||
```
|
||||
|
||||
文件名包含以下关键词也视为敏感:
|
||||
|
||||
```text
|
||||
secret
|
||||
token
|
||||
password
|
||||
credential
|
||||
private
|
||||
apikey
|
||||
api_key
|
||||
```
|
||||
|
||||
## 7. 扫描和索引
|
||||
|
||||
`WorkspaceScanner` 第一版使用 `QDir` + 手动目录栈扫描工作区,后续如需取消、进度回调或更细粒度过滤,再考虑替换为 `QDirIterator` 或异步扫描。
|
||||
|
||||
默认限制:
|
||||
|
||||
- 最大递归深度:8。
|
||||
- 最大文件数量:5000。
|
||||
- 跳过隐藏目录。
|
||||
- 跳过常见产物目录。
|
||||
|
||||
跳过目录:
|
||||
|
||||
```text
|
||||
.git
|
||||
.vs
|
||||
.idea
|
||||
build
|
||||
cmake-build-debug
|
||||
cmake-build-release
|
||||
node_modules
|
||||
dist
|
||||
release
|
||||
release_packages
|
||||
logs
|
||||
```
|
||||
|
||||
`WorkspaceIndex` 第一版只做内存索引:
|
||||
|
||||
```cpp
|
||||
class WorkspaceIndex
|
||||
{
|
||||
public:
|
||||
void clear();
|
||||
void setFiles(const QVector<WorkspaceFileInfo> &files);
|
||||
QVector<WorkspaceFileInfo> allFiles() const;
|
||||
QVector<WorkspaceFileInfo> findByName(const QString &keyword, int maxResults) const;
|
||||
QVector<WorkspaceFileInfo> findBySuffix(const QStringList &suffixes, int maxResults) const;
|
||||
QVector<WorkspaceFileInfo> recentFiles(int maxResults) const;
|
||||
};
|
||||
```
|
||||
|
||||
## 8. 工具清单
|
||||
|
||||
### 8.1 只读工具
|
||||
|
||||
`ListFilesTool`
|
||||
|
||||
- 列出工作区内目录。
|
||||
- 最多显示 200 项。
|
||||
- 不递归或浅递归。
|
||||
|
||||
`ReadFileTool`
|
||||
|
||||
- 只读文本文件。
|
||||
- 最大读取 128KB。
|
||||
- 超出截断。
|
||||
- 敏感文件默认拒绝。
|
||||
- 不读二进制文件。
|
||||
|
||||
支持后缀:
|
||||
|
||||
```text
|
||||
.cpp .h .hpp .c .cc .py .js .ts .json .md .txt .ini .xml .yaml .yml .cmake .pro .css .html
|
||||
```
|
||||
|
||||
`SearchFilesTool`
|
||||
|
||||
- 只搜索当前工作区索引。
|
||||
- 最多返回 30 个结果。
|
||||
- 按相关性和最近修改时间排序。
|
||||
|
||||
`SearchTextTool`
|
||||
|
||||
- 最多扫描 1000 个文本文件。
|
||||
- 单文件最大读取 128KB。
|
||||
- 最多返回 50 条匹配。
|
||||
- 每条显示文件路径、行号、片段。
|
||||
|
||||
### 8.2 打开工具
|
||||
|
||||
`OpenFileTool`
|
||||
|
||||
- 用系统默认程序打开文件。
|
||||
- 只能打开工作区内普通文件。
|
||||
- 危险后缀拒绝。
|
||||
- 敏感文件需要确认或第一版拒绝。
|
||||
|
||||
`OpenDirectoryTool`
|
||||
|
||||
- 打开工作区内目录。
|
||||
- 工作区外路径拒绝。
|
||||
|
||||
`RevealInExplorerTool`
|
||||
|
||||
- 第一版可以先打开所在文件夹。
|
||||
- 如果后续使用 `explorer.exe /select`,必须固定程序路径和参数,不接受聊天文本命令。
|
||||
|
||||
### 8.3 写入工具
|
||||
|
||||
`CreateFileTool`
|
||||
|
||||
- 只能在工作区内创建。
|
||||
- 目标不能已存在。
|
||||
- 文件名必须合法。
|
||||
- 危险后缀拒绝。
|
||||
- 必须确认。
|
||||
|
||||
`CreateDirectoryTool`
|
||||
|
||||
- 只能在工作区内创建。
|
||||
- 目标不能已存在。
|
||||
- 必须确认。
|
||||
|
||||
`WriteFileTool`
|
||||
|
||||
- 修改已有文本文件前自动备份。
|
||||
- 禁止覆盖二进制文件。
|
||||
- 禁止写敏感文件。
|
||||
- 禁止写危险后缀。
|
||||
- 必须确认。
|
||||
|
||||
备份目录:
|
||||
|
||||
```text
|
||||
<workspaceRoot>/.desktoppet/backups/
|
||||
```
|
||||
|
||||
备份命名:
|
||||
|
||||
```text
|
||||
relative_path.replace("/", "__") + "." + yyyyMMdd-HHmmss + ".bak"
|
||||
```
|
||||
|
||||
`CopyFileTool`
|
||||
|
||||
- 只复制单文件。
|
||||
- 源和目标都必须在工作区内。
|
||||
- 不覆盖。
|
||||
- 必须确认。
|
||||
|
||||
`MoveFileTool`
|
||||
|
||||
- 只移动单文件。
|
||||
- 源和目标都必须在工作区内。
|
||||
- 不覆盖。
|
||||
- 必须确认。
|
||||
|
||||
`RenameFileTool`
|
||||
|
||||
- 只允许同目录重命名。
|
||||
- 文件名不能包含路径分隔符。
|
||||
- 不覆盖。
|
||||
- 必须确认。
|
||||
|
||||
`DeleteToTrashTool`
|
||||
|
||||
- 只移动到系统回收站。
|
||||
- 不永久删除。
|
||||
- 单文件删除第一阶段可支持。
|
||||
- 文件夹删除需要强确认。
|
||||
- 批量删除第一阶段拒绝。
|
||||
|
||||
## 9. WorkspaceAgent
|
||||
|
||||
第一版采用半自动 Agent,不做复杂工具循环。
|
||||
|
||||
推荐流程:
|
||||
|
||||
```text
|
||||
用户输入
|
||||
↓
|
||||
WorkspaceController 判断工作区任务
|
||||
↓
|
||||
程序根据关键词和索引检索相关文件
|
||||
↓
|
||||
读取少量相关文件
|
||||
↓
|
||||
把文件树摘要、相关文件内容、用户问题交给模型
|
||||
↓
|
||||
模型返回分析结果或修改建议
|
||||
↓
|
||||
涉及写操作时生成 WorkspaceActionPlan
|
||||
↓
|
||||
用户确认
|
||||
↓
|
||||
执行工具
|
||||
```
|
||||
|
||||
工作区专用系统提示词:
|
||||
|
||||
```text
|
||||
你是 QtDesktopPet 的本地工作区助手。
|
||||
|
||||
你只能基于用户授权的工作区文件进行分析。
|
||||
你不能假设自己看过未读取的文件。
|
||||
你不能要求访问工作区外路径。
|
||||
你不能执行系统命令。
|
||||
你不能生成危险操作指令。
|
||||
涉及创建、修改、移动、重命名、删除文件时,必须先生成清晰的操作计划,等待用户确认。
|
||||
涉及敏感文件、密钥、凭证时,必须提醒风险。
|
||||
回答代码问题时要引用具体文件路径。
|
||||
当信息不足时,说明还需要读取哪些文件。
|
||||
```
|
||||
|
||||
后续可升级为工具循环,但第一版不做。
|
||||
|
||||
## 10. WorkspaceController
|
||||
|
||||
建议接口:
|
||||
|
||||
```cpp
|
||||
class WorkspaceController
|
||||
{
|
||||
public:
|
||||
explicit WorkspaceController();
|
||||
|
||||
bool hasWorkspace() const;
|
||||
QString workspaceRoot() const;
|
||||
|
||||
bool openWorkspace(const QString &rootPath, QString *errorMessage = nullptr);
|
||||
void closeWorkspace();
|
||||
|
||||
WorkspaceToolResult scanWorkspace();
|
||||
WorkspaceToolResult handleMessage(const QString &message);
|
||||
|
||||
bool hasPendingPlan() const;
|
||||
WorkspaceActionPlan pendingPlan() const;
|
||||
WorkspaceToolResult confirmPendingPlan();
|
||||
void cancelPendingPlan();
|
||||
|
||||
private:
|
||||
WorkspaceSession m_session;
|
||||
WorkspaceAgent m_agent;
|
||||
std::optional<WorkspaceActionPlan> m_pendingPlan;
|
||||
};
|
||||
```
|
||||
|
||||
原则:
|
||||
|
||||
- `handleMessage()` 不直接执行高风险写操作。
|
||||
- 需要确认时保存到 `m_pendingPlan`。
|
||||
- UI 调用 `confirmPendingPlan()` 后才执行。
|
||||
- 所有错误通过 `WorkspaceToolResult` 返回,不在工具层弹窗。
|
||||
|
||||
## 11. UI 接入
|
||||
|
||||
聊天入口支持:
|
||||
|
||||
```text
|
||||
打开工作区
|
||||
选择工作区
|
||||
当前工作区
|
||||
扫描工作区
|
||||
分析这个项目
|
||||
搜索当前工作区
|
||||
读取 main.cpp
|
||||
打开第一个
|
||||
```
|
||||
|
||||
如果没有选择工作区,提示用户选择目录。
|
||||
|
||||
气泡只显示短状态:
|
||||
|
||||
```text
|
||||
已打开工作区。
|
||||
正在扫描文件。
|
||||
找到了 12 个相关文件。
|
||||
需要你确认修改计划。
|
||||
操作已完成。
|
||||
```
|
||||
|
||||
复杂结果不全部塞进气泡,当前通过独立 Agent 窗口展示。
|
||||
|
||||
当前独立 Agent 窗口可显示:
|
||||
|
||||
- 当前工作区路径。
|
||||
- 文件树。
|
||||
- 搜索结果。
|
||||
- 已读取文件。
|
||||
- AI 分析结果。
|
||||
- 待确认操作计划。
|
||||
- 执行结果。
|
||||
- 最近操作历史。
|
||||
|
||||
## 12. 意图分发
|
||||
|
||||
后续建议新增:
|
||||
|
||||
```cpp
|
||||
enum class UserIntentType
|
||||
{
|
||||
Chat,
|
||||
Reminder,
|
||||
Weather,
|
||||
Workspace,
|
||||
FileOperation,
|
||||
LaunchApp
|
||||
};
|
||||
```
|
||||
|
||||
意图原则:
|
||||
|
||||
- `Reminder` 保持最高优先级。
|
||||
- 明确天气问答走 `Weather`。
|
||||
- 明确工作区、代码、项目分析走 `Workspace`。
|
||||
- 当前阶段,未明确“工作区/项目/代码”且没有工作区文件名特征的普通文件读写请求仍可走旧 `FileOperation`。
|
||||
- 明确应用名启动走 `LaunchApp`。
|
||||
- 其余走 `Chat` 或输入框联网模式。
|
||||
|
||||
不能用宽关键词简单吞掉所有请求:
|
||||
|
||||
- “打开微信”应走 `LaunchApp`。
|
||||
- “打开文件夹”这种泛请求不走 `Workspace`;“打开 docs / 打开这个项目里的 docs”走 `Workspace`。
|
||||
- “搜索 Qt 最新版本”不应走工作区搜索。
|
||||
- “搜索这个项目里的 main”应走工作区搜索。
|
||||
|
||||
## 13. 迁移步骤
|
||||
|
||||
1. 新增 `src/workspace/`,不改旧 `src/fileops/`。
|
||||
2. CMake 登记 workspace 新文件。
|
||||
3. `PetWindow` 增加 `WorkspaceController` 成员。
|
||||
4. 新增工作区打开、扫描、搜索、读取入口。
|
||||
5. v1b 支持打开和定位;写操作计划和确认框从后续阶段开始。
|
||||
6. 新模块覆盖旧文件操作能力后,`handleFileOperationChatMessage()` 转发到 `WorkspaceController`。
|
||||
7. 确认无旧引用后删除 `src/fileops/`。
|
||||
8. 更新 CMake、README、测试文档。
|
||||
|
||||
删除旧文件前必须执行:
|
||||
|
||||
```text
|
||||
rg "FileOperationManager|FileSandbox|FileBackupManager|src/fileops|FileOperation"
|
||||
```
|
||||
|
||||
确认没有有效代码引用后再删。
|
||||
|
||||
## 14. 日志要求
|
||||
|
||||
允许记录:
|
||||
|
||||
- 打开工作区路径。
|
||||
- 扫描文件数量。
|
||||
- 执行的工具名称。
|
||||
- 操作是否成功。
|
||||
- 错误摘要。
|
||||
|
||||
禁止记录:
|
||||
|
||||
- API Key。
|
||||
- Authorization Header。
|
||||
- 完整敏感文件内容。
|
||||
- `.env` 内容。
|
||||
- 密钥文件内容。
|
||||
- 用户完整隐私文本。
|
||||
|
||||
使用项目已有 `Logger`,不要新增零散调试输出。
|
||||
|
||||
## 15. 测试清单
|
||||
|
||||
### 工作区选择
|
||||
|
||||
- 选择普通项目目录。
|
||||
- 选择空目录。
|
||||
- 选择不存在目录。
|
||||
- 选择 `C:/Windows`,应拒绝。
|
||||
- 选择 `Program Files`,应拒绝。
|
||||
- 选择中文路径。
|
||||
- 选择包含空格的路径。
|
||||
|
||||
### 扫描
|
||||
|
||||
- 扫描普通 Qt 项目。
|
||||
- 扫描包含 build 目录的项目。
|
||||
- 扫描包含 `.git` 的项目。
|
||||
- 扫描大量文件目录。
|
||||
- 扫描中文文件名。
|
||||
- 扫描深层目录。
|
||||
|
||||
要求:
|
||||
|
||||
- 不崩溃。
|
||||
- 不扫系统目录。
|
||||
- 不进入跳过目录。
|
||||
- 结果数量受限制。
|
||||
|
||||
### 搜索文件
|
||||
|
||||
- 搜索 `main`。
|
||||
- 搜索 `.cpp`。
|
||||
- 搜索 `README`。
|
||||
- 搜索不存在文件。
|
||||
- 搜索中文文件名。
|
||||
|
||||
要求:
|
||||
|
||||
- 最多返回 30 条。
|
||||
- 显示相对路径。
|
||||
- 结果可用于后续“打开第几个”。
|
||||
|
||||
### 搜索文本
|
||||
|
||||
- 搜索 `class`。
|
||||
- 搜索 `include`。
|
||||
- 搜索中文关键词。
|
||||
- 搜索不存在关键词。
|
||||
- 搜索超大文件。
|
||||
|
||||
要求:
|
||||
|
||||
- 显示路径、行号、片段。
|
||||
- 跳过二进制文件。
|
||||
- 跳过超大文件。
|
||||
- 不读取敏感文件。
|
||||
|
||||
### 读取文件
|
||||
|
||||
- 读取 `.cpp`。
|
||||
- 读取 `.md`。
|
||||
- 读取 `.json`。
|
||||
- 读取图片应拒绝。
|
||||
- 读取 `.exe` 应拒绝。
|
||||
- 读取 `.env` 应拒绝或强确认。
|
||||
- 读取工作区外文件应拒绝。
|
||||
|
||||
### 创建文件
|
||||
|
||||
- 在工作区创建 `test.txt`。
|
||||
- 创建已存在文件应拒绝。
|
||||
- 创建非法文件名应拒绝。
|
||||
- 创建 `.bat` 应拒绝。
|
||||
- 创建到工作区外应拒绝。
|
||||
|
||||
### 修改文件
|
||||
|
||||
- 修改普通 `.txt`。
|
||||
- 修改 `.cpp`。
|
||||
- 修改前生成备份。
|
||||
- 修改二进制文件应拒绝。
|
||||
- 修改敏感文件应拒绝。
|
||||
|
||||
要求:
|
||||
|
||||
- 必须确认。
|
||||
- 必须备份。
|
||||
- 失败不能破坏原文件。
|
||||
|
||||
### 移动和重命名
|
||||
|
||||
- 移动普通文件。
|
||||
- 移动到已存在目标应拒绝。
|
||||
- 重命名普通文件。
|
||||
- 重命名为非法文件名应拒绝。
|
||||
- 移动到工作区外应拒绝。
|
||||
|
||||
### 删除到回收站
|
||||
|
||||
- 删除普通文件到回收站。
|
||||
- 删除文件夹到回收站。
|
||||
- 删除工作区外文件应拒绝。
|
||||
- 永久删除请求应拒绝。
|
||||
- 批量删除请求第一阶段应拒绝。
|
||||
|
||||
### 对话上下文
|
||||
|
||||
```text
|
||||
用户:搜索 README
|
||||
用户:打开第一个
|
||||
|
||||
用户:搜索 fileops
|
||||
用户:读取第二个
|
||||
|
||||
用户:找一下包含 FileOperationManager 的文件
|
||||
用户:总结这些文件的作用
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- 系统能记住上一轮搜索结果。
|
||||
- “第一个”“第二个”能正确解析。
|
||||
- 上下文过期后要求用户重新选择。
|
||||
|
||||
## 16. 静态推演要求
|
||||
|
||||
每完成一个阶段,需要记录:
|
||||
|
||||
- 入口路径。
|
||||
- 业务模块调用链。
|
||||
- 路径校验点。
|
||||
- 用户确认点。
|
||||
- 失败回滚策略。
|
||||
- 不支持能力的拒绝话术。
|
||||
- 对提醒、天气、联网模式、应用启动的回归影响。
|
||||
|
||||
## 17. 最终验收标准
|
||||
|
||||
本轮重构完成后至少达到:
|
||||
|
||||
1. 用户可以选择一个本地项目目录作为工作区。
|
||||
2. 程序可以扫描工作区并建立文件索引。
|
||||
3. 用户可以通过对话搜索文件。
|
||||
4. 用户可以通过对话读取和分析文本文件。
|
||||
5. 用户可以要求分析项目结构。
|
||||
6. 用户可以创建文本文件和文件夹。
|
||||
7. 用户可以移动、重命名、删除到回收站。
|
||||
8. 所有写操作必须展示计划并等待确认。
|
||||
9. 所有路径必须限制在工作区内。
|
||||
10. 不允许永久删除。
|
||||
11. 不允许执行脚本或命令。
|
||||
12. 不允许默认读取敏感文件。
|
||||
13. 旧文件操作功能被新 Workspace 系统替代。
|
||||
14. 项目可以由用户手动构建通过。
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -58,6 +58,7 @@ reports/perf/
|
||||
| 联网模式 | 使用支持联网的 OpenAI/Gemini Provider 连续发起 5 次联网对话,并覆盖 DeepSeek/Custom 不支持提示 | `tools/perf_sample.ps1 -DurationSeconds 300` | 请求异步执行,来源展示不阻塞 UI;不支持 Provider 不发起伪联网;日志不记录完整用户问题或 API Key | TODO | TODO | 输入框打开联网开关;不再测试旧多搜索源/SearXNG 路径 |
|
||||
| 应用启动 | 登记一个测试 exe,聊天触发启动并取消/确认各一次 | 手工观察 + 采样 | 启动前始终二次确认;取消时不启动;确认后 UI 可继续响应;不执行脚本或聊天参数 | TODO | TODO | 使用无副作用测试程序,例如记事本或临时测试 exe |
|
||||
| 本地文件操作 | 读取文本、列目录、复制、备份、重命名 | 手工观察 + 采样 | 写操作有确认;不覆盖、不删除、不访问系统目录;操作后 UI 可继续响应 | TODO | TODO | 使用临时测试目录,不操作真实重要文件 |
|
||||
| 本地工作区 Agent v1b.5 | 选择临时项目工作区,扫描、搜索文件、搜索文本、读取文件、总结文件、打开普通文件、显示所在位置,并使用独立 Agent 窗口、右键/托盘入口和 `Alt+A` 快捷键操作选中结果 | 手工观察 + `tools/perf_sample.ps1 -DurationSeconds 600` | 扫描和搜索结果数量受限制;独立窗口刷新不阻塞 UI;快捷键注册失败有提示且菜单入口可用;打开文件前二次确认;系统目录、符号链接、危险后缀、敏感文件和命令执行均拒绝;日志不记录敏感文件内容;内存无持续异常增长 | TODO | TODO | 不做创建、修改、移动、删除;使用临时测试仓库,不操作真实重要项目 |
|
||||
| 配置损坏兜底 | 备份后分别破坏 app 配置、AI 配置或本地聊天记录再启动 | 启动后采样 3 分钟 | 程序恢复默认配置或忽略损坏历史,并生成带时间戳的 broken 备份,不覆盖旧备份 | TODO | TODO | 使用备份副本测试 |
|
||||
| 角色包损坏兜底 | 使用临时复制的损坏角色包测试 | 启动后采样 3 分钟 | 程序不崩溃,回退 preview 或默认显示 | TODO | TODO | 不直接破坏仓库内默认角色包 |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user