diff --git a/README.md b/README.md index 9e9112c..d4a9686 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ TrayTidy 是面向 Windows 11 x64 的本地后台应用整理工具。它常驻系统托盘;左键点击图标后,会在任务栏右下角打开一个简洁窗口,直接列出当前用户应用,并标注“正在活动”“建议清理”“可考虑清理”或“建议保留”。 -系统组件默认隐藏且不会成为第一层清理对象。程序完全离线,不读取用户内容,也不会自动结束任何应用。 +系统组件默认隐藏且不会成为第一层清理对象。核心扫描与清理默认完全本地;可选 AI 应用说明功能默认关闭,仅在用户主动配置并启用后访问指定模型接口。程序不读取用户内容,也不会自动结束任何应用。 ## 界面预览 @@ -42,13 +42,23 @@ TrayTidy 是面向 Windows 11 x64 的本地后台应用整理工具。它常驻 TrayTidy **不扫描音频**,不注册前台使用跟踪 Hook,不持续采样资源,也不保存长期使用轨迹。 +## 可选 AI 应用说明 + +主界面的“AI 说明”是一个独立显示开关:默认收起以保持紧凑行高,打开时按当前列表刷新应用用途和条件式文字建议,关闭时取消当前请求并折叠全部说明,但不会删除有效缓存。“AI 说明”可连接用户指定的 OpenAI-compatible Chat Completions 接口,默认不访问网络,也不会改变本地推荐评分、推荐等级、默认勾选、用户勾选、保护规则或关闭行为;`RecommendationEngine` 和关闭前安全复核始终是这些行为的唯一依据。扫描会先立即显示本地结果,AI 缓存与网络请求在独立支路异步执行。 + +localhost、127.0.0.1 和 `[::1]` 接口可以使用 HTTP 或 HTTPS;远程接口必须使用 HTTPS,并由用户明确勾选允许远程访问。API Key 可留空以支持本地无认证接口;非空凭据只保存到 Windows Credential Manager 的 `TrayTidy/AiAdvisorApiKey`,不会写入设置、SQLite、日志、URL、缓存键或请求正文。 + +会发送给模型的数据仅包括:应用显示名、exe 文件名、去重后的进程文件名列表、产品/文件/厂商描述、包系列名称、进程数量、内存/CPU/I/O 档位、窗口和活动状态、本地推荐等级及历史关闭/保留次数汇总。 + +不会发送:AppKey、完整路径、PID、精确启动时间、Windows 用户名、窗口标题、命令行、环境变量、文件内容、聊天/文档/浏览器页面、API Key、Token 或原始日志。模型输出按不可信外部输入处理,并在展示前再次经过本地安全降级。 + ## 隐私与安全 - 不采集键盘、鼠标、剪贴板、命令行、完整窗口标题、聊天、文档、浏览器页面、网络请求、密钥或文件内容。 -- 不联网,不含遥测、更新检查或崩溃上传。 +- 默认不联网,不含遥测、更新检查或崩溃上传;仅在用户启用 AI 说明后访问用户指定的模型接口。 - 系统关键进程、Session 0、其他用户会话和 Windows 组件受硬保护并从主列表隐藏。 - 关闭前重新验证 PID 与进程启动时间,避免 PID 复用导致误操作。 -- 执行前重新检查前台窗口、疑似模态对话框,并短时复核 CPU/I/O 活动;状态改变时取消操作。 +- 执行前重新检查前台窗口和疑似模态对话框;CPU/I/O 活动只影响推荐和默认勾选,不阻断用户明确发起的正常关闭请求。 - 优先发送正常退出请求;只有进程仍存在且用户再次确认“彻底终止”时,才会强制结束进程树。 - 窗口弹出、扫描完成和界面内操作提示均不播放提示音。 - 日常运行保持 `asInvoker` 且不使用 `SeDebugPrivilege`;仅当用户明确选择彻底终止且权限不足时,发起一次 Windows UAC 请求。提权请求会校验内容完整性。 @@ -82,6 +92,7 @@ TrayTidy **不扫描音频**,不注册前台使用跟踪 Hook,不持续采 - `src/TrayTidy.App`:WPF 界面、托盘和应用生命周期。 - `src/TrayTidy.Core`:应用分组、推荐规则及安全契约。 - `src/TrayTidy.Infrastructure.Windows`:Windows 进程、窗口、退出和启动项实现。 +- `src/TrayTidy.Infrastructure.AI`:可选 AI 说明的脱敏、端点校验、HTTP 协议、响应解析、安全降级和缓存指纹。 - `src/TrayTidy.Persistence`:本地 SQLite 设置与决策记录。 - `tests/`:核心逻辑、持久化和 Windows 安全边界测试。 - `docs/`:架构、安全边界与手工测试说明。 @@ -96,7 +107,7 @@ TrayTidy **不扫描音频**,不注册前台使用跟踪 Hook,不持续采 ## 本地数据 -规则、设置和关闭结果位于 `%LocalAppData%\TrayTidy\traytidy.db`,日志位于 `%LocalAppData%\TrayTidy\logs\`。日志最多保留 7 天,关闭结果最多保留 30 天,均不记录用户内容。旧版本遗留的数据库表不会被读取或继续写入。 +规则、设置、关闭结果和经过验证的 AI 说明缓存位于 `%LocalAppData%\TrayTidy\traytidy.db`,日志位于 `%LocalAppData%\TrayTidy\logs\`。模型凭据不进入 SQLite。日志最多保留 7 天,关闭结果最多保留 30 天,均不记录用户内容、完整模型请求或响应。旧版本遗留的数据库表不会被读取或继续写入。 ## 已知限制 diff --git a/TrayTidy.sln b/TrayTidy.sln index 9e26b7b..7d4e020 100644 --- a/TrayTidy.sln +++ b/TrayTidy.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrayTidy.Core", "src\TrayTi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrayTidy.Infrastructure.Windows", "src\TrayTidy.Infrastructure.Windows\TrayTidy.Infrastructure.Windows.csproj", "{A28A3079-8CAE-4B0E-88F9-EABF4FAF4E0E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrayTidy.Infrastructure.AI", "src\TrayTidy.Infrastructure.AI\TrayTidy.Infrastructure.AI.csproj", "{D88A94C0-C02C-4B3B-88C1-0715D314B770}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrayTidy.Persistence", "src\TrayTidy.Persistence\TrayTidy.Persistence.csproj", "{0C004578-7F99-4C82-A631-C851566BDEC9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" @@ -21,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrayTidy.Persistence.Tests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrayTidy.Windows.IntegrationTests", "tests\TrayTidy.Windows.IntegrationTests\TrayTidy.Windows.IntegrationTests.csproj", "{462D9F2E-39BE-4931-9249-11ADEE9EACDA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrayTidy.Infrastructure.AI.Tests", "tests\TrayTidy.Infrastructure.AI.Tests\TrayTidy.Infrastructure.AI.Tests.csproj", "{F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +71,18 @@ Global {A28A3079-8CAE-4B0E-88F9-EABF4FAF4E0E}.Release|x64.Build.0 = Release|Any CPU {A28A3079-8CAE-4B0E-88F9-EABF4FAF4E0E}.Release|x86.ActiveCfg = Release|Any CPU {A28A3079-8CAE-4B0E-88F9-EABF4FAF4E0E}.Release|x86.Build.0 = Release|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Debug|x64.ActiveCfg = Debug|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Debug|x64.Build.0 = Debug|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Debug|x86.ActiveCfg = Debug|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Debug|x86.Build.0 = Debug|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Release|Any CPU.Build.0 = Release|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Release|x64.ActiveCfg = Release|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Release|x64.Build.0 = Release|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Release|x86.ActiveCfg = Release|Any CPU + {D88A94C0-C02C-4B3B-88C1-0715D314B770}.Release|x86.Build.0 = Release|Any CPU {0C004578-7F99-4C82-A631-C851566BDEC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0C004578-7F99-4C82-A631-C851566BDEC9}.Debug|Any CPU.Build.0 = Debug|Any CPU {0C004578-7F99-4C82-A631-C851566BDEC9}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -115,6 +131,18 @@ Global {462D9F2E-39BE-4931-9249-11ADEE9EACDA}.Release|x64.Build.0 = Release|Any CPU {462D9F2E-39BE-4931-9249-11ADEE9EACDA}.Release|x86.ActiveCfg = Release|Any CPU {462D9F2E-39BE-4931-9249-11ADEE9EACDA}.Release|x86.Build.0 = Release|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Debug|x64.Build.0 = Debug|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Debug|x86.ActiveCfg = Debug|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Debug|x86.Build.0 = Debug|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Release|Any CPU.Build.0 = Release|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Release|x64.ActiveCfg = Release|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Release|x64.Build.0 = Release|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Release|x86.ActiveCfg = Release|Any CPU + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -123,9 +151,11 @@ Global {E4A87D1E-4849-4FE5-8FA6-12E57C09BE43} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {9F32AA0E-832D-48A9-96DF-0F67D2A3E70B} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {A28A3079-8CAE-4B0E-88F9-EABF4FAF4E0E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {D88A94C0-C02C-4B3B-88C1-0715D314B770} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {0C004578-7F99-4C82-A631-C851566BDEC9} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {0E709AA8-628A-4941-A681-A6DC2B58CDF3} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {B420E96A-27EB-481E-A087-B313BF691829} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {462D9F2E-39BE-4931-9249-11ADEE9EACDA} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {F8DAE6EF-A3BA-42D0-BC29-FAD7A2AB39F2} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection EndGlobal diff --git a/build.ps1 b/build.ps1 index 5c1c811..a70d018 100644 --- a/build.ps1 +++ b/build.ps1 @@ -8,6 +8,9 @@ try { dotnet restore TrayTidy.sln if ($LASTEXITCODE -ne 0) { throw "dotnet restore failed with exit code $LASTEXITCODE." } + dotnet format TrayTidy.sln --verify-no-changes --no-restore --verbosity minimal + if ($LASTEXITCODE -ne 0) { throw "dotnet format failed with exit code $LASTEXITCODE." } + dotnet build TrayTidy.sln -c Release --no-restore if ($LASTEXITCODE -ne 0) { throw "dotnet build failed with exit code $LASTEXITCODE." } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a38e9f7..5c45aae 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,10 +1,11 @@ # 架构说明 -TrayTidy 采用四个产品项目,界面与 Windows 平台代码通过 Core 接口隔离: +TrayTidy 采用五个产品项目,界面、AI 与 Windows 平台代码通过 Core 接口隔离: - `TrayTidy.App`:WPF 紧凑应用列表、保护列表 Tab、托盘、MVVM 命令与生命周期。 - `TrayTidy.Core`:应用分组、推荐、行内提醒、规则和安全契约。 - `TrayTidy.Infrastructure.Windows`:进程/窗口快照、短时 CPU 与 I/O 采样、应用激活、正常退出、系统保护、单实例与启动项。 +- `TrayTidy.Infrastructure.AI`:OpenAI-compatible HTTP 客户端、脱敏描述符、端点校验、结构化响应解析、本地安全降级和缓存指纹。 - `TrayTidy.Persistence`:SQLite 设置、规则、决策与关闭结果。 ## 扫描流程 @@ -15,7 +16,15 @@ TrayTidy 采用四个产品项目,界面与 Windows 平台代码通过 Core 4. 对每个应用生成状态、简短理由及必要的行内提醒。 5. 系统保护组只计入“已隐藏”数量,不进入可操作列表。 -不存在周期扫描、音频枚举、前台 Hook、长期使用会话或网络调用。 +不存在周期扫描、音频枚举、前台 Hook 或长期使用会话。确定性扫描流程不包含 AI 或其他网络调用。 + +## AI 旁路 + +AI 说明不进入 `OnDemandScanService`、`RecommendationEngine`、`ApplicationTerminationService` 或提权终止链路。Core 中的 `AiApplicationDescriptor` 是唯一允许发送的应用元数据结构,不含 AppKey、路径、PID、用户名、窗口标题、命令行或用户内容。`TrayTidy.Infrastructure.AI` 只序列化该描述符;Windows API 凭据实现留在 `TrayTidy.Infrastructure.Windows`。 + +本地扫描完成后,`MainViewModel` 立即替换应用列表。主界面的“AI 说明”采用会话级显示开关并默认关闭;打开时协调器按当前列表读取匹配的 SQLite 缓存,并将未命中缓存的条目一次提交,验证模型完整返回后再按请求 ID 分发到各应用行。关闭开关会取消当前请求并折叠每个应用的 AI 区域,但不删除有效缓存。只有开关保持打开时,扫描后的自动分析设置才会生效。每次扫描递增 generation;进度回调只有在 generation 仍匹配时才更新应用行。新扫描、清理、退出或新分析会取消旧任务,同一时间最多存在一个 AI 分析任务。 + +Provider fingerprint 仅由规范化 endpoint、model 和 prompt version 计算;缓存键另包含脱敏身份、活动档位和本地推荐等级,不包含 API Key。模型或 endpoint 改变、prompt version 升级、活动档位变化或缓存过期都会阻止旧结果复用。SQLite 只保存验证和本地安全降级后的 insight,不保存原始请求或原始响应。 ## 生命周期 @@ -23,6 +32,6 @@ TrayTidy 采用四个产品项目,界面与 Windows 平台代码通过 Core ## 关闭契约 -主界面先执行正常关闭:执行前重新快照并验证 AppKey、PID、启动时间、系统保护和前台/模态状态,再进行约 1 秒 CPU/I/O 复核,之后才向目标顶层窗口发送 `WM_CLOSE` 并等待。进度通过主窗口内的非模态动画条显示。只有返回“仍在运行”时,界面才提供可忽略的“彻底终止进程”按钮;用户点击后再次执行同样的身份、安全和活动复核,再终止进程树。不会静默升级。普通权限不足时,提权请求文件的 SHA-256 摘要随启动参数传递,管理员进程仅处理内容完整且带有显式强杀授权的请求。 +主界面先执行正常关闭:执行前重新快照并验证 AppKey、PID、启动时间、系统保护和前台/模态状态,之后向目标顶层窗口发送 `WM_CLOSE` 并等待。CPU/I/O 和子进程活动只影响推荐与默认勾选;用户明确点击关闭后不会被后台常驻活动拦截,应用仍可自行拒绝退出或显示保存确认。进度通过主窗口内的非模态动画条显示。只有返回“仍在运行”时,界面才提供可忽略的“彻底终止进程”按钮;用户点击后再次执行身份、系统保护和前台/模态复核,再终止进程树。不会静默升级。普通权限不足时,提权请求文件的 SHA-256 摘要随启动参数传递,管理员进程仅处理内容完整且带有显式强杀授权的请求。 只有确认成功退出后才会写入最近一次关闭决策,并对下一次安全扫描提供一次性推荐提升;失败、状态变化或用户忽略不会伪装成成功关闭。如果用户在后续清理中未勾选该推荐项,则记录 `Ignore`,使一次性提升失效。明确的长期关闭/保留统计仍可作为较弱的历史信号,关闭结果在启动时清理至最近 30 天。 diff --git a/docs/MANUAL_TESTS.md b/docs/MANUAL_TESTS.md index 2317ff5..4151a26 100644 --- a/docs/MANUAL_TESTS.md +++ b/docs/MANUAL_TESTS.md @@ -26,7 +26,8 @@ 2. 确认不弹出模态确认框;主界面轻微缩小并变暗,中央出现蓝紫能量环、已选应用数量和扫描光带,处理完成后显示勾选反馈并平滑回到列表。 3. 对会缩小到自身托盘的应用测试,确认窗口内出现可忽略的“彻底终止”按钮。 4. 点击“忽略”,确认进程保留;再次测试并点击“彻底终止”,确认在身份复核后终止进程树并记录结果。 -5. 在确认前将目标切到前台、启动 CPU/I/O 任务或打开保存对话框,确认操作被复核并取消。 +5. 在确认前将目标切到前台或打开保存对话框,确认操作被复核并取消。 +6. 对后台持续产生 CPU/I/O 或包含 CefSharp/Chromium 活动子进程的普通用户应用明确点击“关闭”,确认仍会发送正常退出请求;该活动仍不得使应用被默认勾选。 6. 确认系统组件不会出现在可勾选列表中。 7. 清理过程中点击托盘“重新扫描”,确认不会刷新列表或与当前操作并发。 @@ -47,3 +48,16 @@ 2. 用媒体应用播放音频,确认 TrayTidy 不枚举音频设备或音频会话;推荐仅依据进程/窗口和短时资源活动。 3. 检查本地数据库,确认不再创建或写入前台使用记录。 4. 开启登录启动,检查 HKCU Run 值包含带引号的完整路径和 `--background`;关闭设置后确认值被删除。 + +## 可选 AI 应用说明 + +1. 保持 AI 关闭,扫描、筛选和关闭应用;用网络监视工具确认没有模型请求,原有行为不变。 +2. 配置 localhost OpenAI-compatible Chat Completions 接口(允许 HTTP),不填写 API Key,确认“测试连接”和手动“AI 说明”可用。 +3. 配置远程 HTTPS 接口,确认未勾选远程确认时不能启用;勾选后才能保存并请求。确认远程 HTTP、含 user-info 的 URL 和无效协议均被本地拒绝。 +4. 保存、替换和删除 API Key;重新打开设置窗口时只显示“已保存凭据”,不会回填明文。检查 Settings JSON、SQLite 和日志均无凭据。 +5. 检查设置窗口的“会发送/不会发送”清单;抓取测试服务收到的请求,确认没有完整路径、PID、用户名、窗口标题、命令行、文件内容、AppKey 或 API Key 正文。 +6. 让测试服务返回非法 JSON、未知枚举、重复 ID、超长文本和超大响应,确认应用不崩溃,只提示“AI 分析未完成,本地推荐不受影响”。 +7. 让测试服务超时;分析期间重新扫描,确认旧请求取消且旧 generation 结果不显示。退出应用时确认请求也被取消。 +8. 记录分析前的复选框、本地状态、分数和保护规则;分析成功或失败后逐项确认完全不变。对前台、模态窗口、活动子任务、AlwaysKeep 和身份不可靠应用确认不会显示关闭建议。 +9. 确认主界面“AI 说明”默认是关闭状态且应用行保持紧凑;打开后立即刷新并展开说明,关闭后当前请求取消、所有说明折叠且行高恢复。重新打开时确认有效缓存命中且不重复请求网络;改变 model 或 endpoint 后确认旧缓存不复用。 +10. 点击“清除 AI 缓存”,确认说明消失且下次需要重新生成;重启后确认模型接口启用设置仍保留,但主界面 AI 说明开关恢复关闭,API Key 仍只存在 Credential Manager。 diff --git a/docs/SAFETY.md b/docs/SAFETY.md index e93afb3..7882264 100644 --- a/docs/SAFETY.md +++ b/docs/SAFETY.md @@ -13,7 +13,7 @@ TrayTidy 给出的是短时、可解释的整理建议,不是应用可以无 系统关键进程、TrayTidy 自身、Session 0、其他用户会话、Windows 目录组件、身份或路径不可验证的进程不会进入可操作主列表。 -当前前台、刚启动、存在活动子进程、短时 CPU/I/O 活跃、疑似模态对话框、用户设为保护的应用不会被默认推荐。活动状态不会禁用普通用户应用的手动复选框;实际关闭时仍会重新执行身份、系统和状态复核,并短时重新采样 CPU/I/O。 +当前前台、刚启动、存在活动子进程、短时 CPU/I/O 活跃、疑似模态对话框、用户设为保护的应用不会被默认推荐。活动状态不会禁用普通用户应用的手动复选框,也不会拦截用户明确发起的正常关闭请求;实际关闭时仍会重新执行身份、系统、前台和模态状态复核。 ## 权限与退出 @@ -21,10 +21,16 @@ TrayTidy 给出的是短时、可解释的整理建议,不是应用可以无 - 不写 HKLM;可选启动项只写当前用户 HKCU。 - 关闭前重新验证 PID 和启动时间,避免 PID 复用。 - 正常关闭失败不会自动调用 Kill;仅在用户对“仍在运行”的项目明确确认“彻底终止”后执行一次。 -- 强制终止前再次验证 PID、启动时间、系统保护、前台/模态状态和资源活动;不会无限重试。仅当用户明确点击“彻底终止”且普通权限不足时发起一次 UAC 请求。 +- 强制终止前再次验证 PID、启动时间、系统保护和前台/模态状态;不会无限重试。仅当用户明确点击“彻底终止”且普通权限不足时发起一次 UAC 请求。 - 提权请求内容使用 SHA-256 摘要与启动参数绑定;提权进程拒绝内容被替换的请求,也不会覆盖预先存在的响应文件。 - 正常清理不使用模态对话框;进度、失败和可选的彻底终止入口都显示在主窗口内,且不触发提示音。 ## 隐私 -程序不含音频扫描、前台使用记录、网络客户端、遥测、更新检查或崩溃上传。禁止采集键鼠、剪贴板、窗口标题内容、命令行、聊天、文档、浏览器页面、网络请求、API Key、Token 或用户文件内容。日志只记录应用自身阶段和异常类型,最多保留 7 天;关闭结果最多保留 30 天。 +程序不含音频扫描、前台使用记录、遥测、更新检查或崩溃上传。可选 AI 说明默认关闭;关闭时不产生模型网络请求,启用后也只访问用户明确配置的 endpoint。 + +模型输出属于不可信外部输入。解析器严格校验 RequestId、枚举和必要字段,限制响应及文本长度,忽略未知或重复 ID,并拒绝确定性安全声明或操作指令。任何 `close_if_unused` 建议在展示前都要经过本地规则复核;前台、模态窗口、活动子进程、保护规则、身份不可靠或其他本地阻断条件会将其降级为本地生成的保留/未知文字。AI 结果不会写入 `Recommendation`、`IsSelected`、`AppRule`,也不会传递给任何正常或提权终止服务。 + +发送数据在专门的描述符构建器中脱敏:不发送 AppKey、完整路径、PID、精确启动时间、用户名、窗口标题、命令行、环境变量、文件内容、聊天、文档、浏览器页面或原始日志。除用户主动配置给 TrayTidy 的模型凭据外,程序不读取环境变量、配置文件或其他应用中的 API Key/Token。 + +模型凭据使用 Windows Credential Manager 保存,不进入 Settings JSON、SQLite、日志、URL、缓存键、异常信息或请求正文,只用于 `Authorization: Bearer` 请求头。程序不记录完整 AI 请求、完整响应或 Authorization 头。日志只允许记录 HTTP 状态码、耗时、条目数量和异常类型;关闭结果最多保留 30 天。 diff --git a/docs/images/traytidy-overview.jpg b/docs/images/traytidy-overview.jpg index 3f16f16..4adb385 100644 Binary files a/docs/images/traytidy-overview.jpg and b/docs/images/traytidy-overview.jpg differ diff --git a/src/TrayTidy.App/AiSettingsWindow.xaml b/src/TrayTidy.App/AiSettingsWindow.xaml new file mode 100644 index 0000000..b6cb233 --- /dev/null +++ b/src/TrayTidy.App/AiSettingsWindow.xaml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +