本文档记录每一步做到哪了、改了什么、下一步做什么。版本改动明细见 CHANGELOG.md。
| 版本 | 目标 | 状态 |
|---|---|---|
| 2.0 | 全面排查 bug 并修复,发布第二个大版本;跑通"取 Token → 校验 → 写入 → 备份还原"全流程 | ✅ 已发布(提交 0e2b338,tag v2.0,单文件版留档 releases/2.0/) |
| 2.1 | 发布后校验"将要写入模型文件的内容"是否合法;一键还原 / 手动备份按钮;增多可配置项 | ✅ 已完成,待提交(tag v2.1) |
| 2.2 | 再一轮大范围 bug 排查与加固 | ✅ 已完成(tag v2.2,单文件版留档 releases/2.2/) |
| 2.3 | 修"发消息报 reasoning effort medium 不被接受";思考档不再写模型不支持的档位 | ✅ 已完成(tag v2.3) |
| 2.4 | 首次运行直接开窗口;首次备份永不清;默认自动同步/180 分钟/不弹提醒;Token 显示到期时刻;桌面快捷方式 | ✅ 已发布(tag v2.4);设置界面有 bug,勿用,见 2.4.1 |
| 2.4.1 | 修「设置界面打不开」;把创建桌面快捷方式提到主界面;大检查 | ✅ 已完成(tag v2.4.1,留档 releases/2.4.1/) |
| 2.5 | 修"上一次同步还没结束"卡死 + 强行终止并重试;恢复默认设置;private 位置可改;干净卸载;启动提醒 | ✅ 已完成(需求见 docs/REQUIREMENTS-2.5.md) |
| 2.5.1 | 真机反馈修复:释放被占用的 browser-profile/logs/app.lock;删除改走回收站;重启不再被判"已在运行";提醒提前到创建 private 之前 | ✅ 已完成(留档 releases/2.5.1/) |
| 2.5.2 | 全面检修:修"同标签备份挤一个目录"、自动同步误弹窗、维护期被插入、恢复默认绕过安全流程、prune 未走回收站、提醒仍建目录、重复启动干等 | ✅ 已完成(留档 releases/2.5.2/) |
| 2.5.3 | 修干净卸载 logs 删不掉(日志 handler 泄漏的真根因);同步不再改用户设的思考档(除非不合法) | ✅ 已完成(留档 releases/2.5.3/) |
| 2.5.4 | 源码复检:坏模型缓存不再搞挂同步;同步线程收尾不再串台 | ✅ 已完成(留档 releases/2.5.4/) |
| 2.5.5 | 修"同步把别的模型的 api 改了":不再认领别人的本地转发 provider;写前二次核对;CC Switch 只刷 Token 字段 | ✅ 已完成(留档 releases/2.5.5/) |
| 2.5.6 | 修"已经带了我们标记的别人的 provider 仍被继续改"(isearcher/infini-ai):去掉 apiKeyEnv 认领、收紧 profile 归属、加误改提醒 | ✅ 已完成(留档 releases/2.5.6/) |
| 2.5.7 | 写入面加固:块级/行级替换(别人配置的排版也不许动)、旧卡片刷新改默认关、清理 7 处死逻辑 | ✅ 已完成(留档 releases/2.5.7/) |
| 2.5.8 | 死逻辑收尾:复用首次备份判定、卸载走统一指针清理入口、删掉只剩测试在用的 describe_remaining | ✅ 已完成(留档 releases/2.5.8/) |
| 2.5.9 | 发布前全量体检:DSH 新增 provider 不再整份重写、CC Switch JSON 最小插入、预检全局失败阻塞写入、坏 JSON 不再崩启动、搬家失败回滚、死逻辑/死参数归零 | ✅ 已完成(留档 releases/2.5.9/) |
| 2.6.0 | 正式版打磨:Qt 中文标准按钮、CC Switch 运行策略合并(设置 v3→v4 迁移)、恢复默认同步所有控件、关于对话框、预检后台化、桌面提醒不重复、新增使用手册 | ✅ 已完成(留档 releases/2.6.0/) |
| 2.6.1 | 真机三条反馈:DSH 坏 baseURL 纠正(认领时)、开机自动更新整链路加固(延后/预检/重试/静默失败弹窗/日志兜底)、CC Switch 提交前读回校验 | ✅ 已完成(留档 releases/2.6.1/) |
| 2.7.0 | 问题清单 10 组逐项处理:DPAPI、可选自动登录、60±10 分钟、JWT 候选、DSH/CC Switch 写入拆分、字段级还原、界面重整、版本信息 | ✅ 已发布(tag v2.7.0,留档 releases/2.7.0/) |
| 2.7.1 | 真机回归:自动登录隐藏验证码误判、<a> 登录按钮未提交、后台登录不应弹窗;设置窗口缩小并记住主/设置窗口尺寸 |
✅ 已完成(留档 releases/2.7.1/) |
| 2.7.2 | 主界面恢复日志(完整/同步结果/关闭)、修复设置内预检使用旧配置且结果被遮挡、按内容重整五个设置页 | ✅ 已完成(留档 releases/2.7.2/) |
| 2.7.3 | 修复打包版 ssl.py 缺少 codex-rejected-cas.pem 导致 HTTPS 初始化异常、成功登录后同步中断 |
✅ 已完成(留档 releases/2.7.3/) |
| 2.8.0 | 资源占用优化:释放浏览器会话、限制 HTTP 缓存、日志按天清理、复用 TLS 上下文、精简打包调试资源 | ✅ 已完成(tag v2.8.0) |
| 2.9.0 | 启动结构优化:WebEngine 惰性导入、首次备份/探测后台化、原生进程快照、多文件 ZIP 主发布 | ✅ 已完成(tag v2.9.0) |
| 2.9.1 | 开机后的启动等待允许手动同步打断;仍先完成原始备份并保留未安装提醒 | ✅ 已完成(tag v2.9.1) |
多文件 ZIP 是正式主资产,单文件 EXE 作为兼容备用;两者都要留档在 releases/<版本>/
(产物不进 Git,使用 releases/<版本>/SHA256.txt 记录校验和;GitHub Release 资产本身支持更大的附件)。
- 开发环境:
D:\2\program\codex\autoThuDeepSeekAPI\autoThuDeepSeekAPI,.venv里装 PySide6 6.12 / PyYAML / ruamel.yaml / PyInstaller 6.22。 - 测试:
python -m unittest discover -s tests -t .(当前 201 项)。 - 打包:
powershell -File build\build_app.ps1(同时出版本化多文件 ZIP、展开目录与单文件版)。 - 自检:
autoThuDeepSeekAPI.exe --selftest(起本地网页写假 Token,验证捕获链路)。 - 运行数据全部在程序目录的
private/,不进 Git;dist/、releases/也不进 Git。
- 安全:Token、备份、变更日志改 DPAPI;浏览器档案当前用户 ACL;自动登录凭据进 Windows Credential Manager。
- 自动同步默认 60 分钟并随机 ±10;JWT 多候选评分;最小对话校验默认关闭。
- DSH 移除全 profile 统一,只认键名
madmodel;没有受管 profile 时只自动创建一份; 凭据与 Provider 分开关;默认思考档缺失时不擅自补。 - CC Switch 卡片与路由激活分开关,路由默认关;路由失败跨文件回滚。
- 字段级变更日志与冲突保护;旧备份整文件还原需额外高风险确认。
- 主窗口、设置五页分类、托盘五项、应用/确定/取消;路径探测增加环境变量和注册表安装位置。
- EXE 文件版本资源;测试 148 → 168 项全绿。
- 打包文件夹版与单文件版,分别跑
--selftest; - 用真实 DSH / CC Switch 做一次只读预检和真实设备体检(不自动修改);
- 留档
releases/2.7.0/、更新 SHA256、提交并打 tag。
- 自动登录根因:清华统一身份认证页在 DOM 中始终保留一个隐藏的验证码输入框,
旧脚本把它误判成人工验证;页面「登录」实际是
<a onclick="doLogin()">,旧脚本只找 button,所以即使填了密码也不会提交。现在只认真实可见的验证码,并覆盖可见的 button/input/a/role=button 登录控件。 - 浏览器弹出行为:保存自动登录凭据后,手动同步也先使用隐藏的后台窗口尝试登录; 只有验证码、二次验证、账号密码错误或页面异常时才显示窗口。
- 窗口尺寸:设置窗口默认
700×540、最小640×480;主窗口和设置窗口尺寸存进private/state.json,重启后恢复,并按屏幕可用区域限制。 - 验证:相关 42 项测试首轮全绿,随后跑全量测试、两版打包与自检。
- 主界面日志:主窗口底部恢复运行日志,通过
log_bus接收后台线程日志;设置支持 “完整日志 / 仅同步结果与时间 / 关闭”三档,最多保留 1200 行。 - 修复预检:设置里的「校验写入内容」改为校验当前对话框草稿,结果框优先显示在设置窗口上; 运行时按钮禁用并显示“正在校验…”,成功用信息框、失败用警告框。
- 设置分页重整:内容按实际用途归入「同步与启动 / 写入目标 / 登录与 Token / 备份、日志与维护 / 应用与工具」,并加回归测试断言控件归属。
- 验证:全量 181 项测试全绿;主界面、设置首页、维护页截图核对;两版打包与自检。
- 根因:构建运行时定制的
ssl.py创建SSLContext时固定读取codex-rejected-cas.pem;PyInstaller 未自动包含该数据文件,于是模型列表请求在 TLS 初始化阶段抛出FileNotFoundError。 - 打包修复:把证书文件放到
_MEIPASS根目录,同时保留在assets/。 - 运行时兜底:新增
http_tls.py,默认 CA 初始化失败时从 WindowsCA/ROOT证书库加载根证书,仍保持证书链与主机名校验。模型下载、Token 校验、站点探测全部使用该入口。 - 防崩:TLS 初始化异常会被转换为“本次模型读取失败 / Token 暂无法校验”, 同步会回退内置模型继续,不再整次失败。
- 验证:全量 187 项测试全绿;强制模拟缺 CA 后实际连接 MadModel 返回 HTTP 200;
两版打包、包内证书文件、
--selftest与文件版本核对通过。
- 浏览器内存:
TokenCaptureSession.finish()显式释放页面与自身,主窗口在取 Token 结束后延迟释放QWebEngineProfile;不再把每次同步的旧页面挂在主窗口下。 - 浏览器存储:HTTP 磁盘缓存上限设为 32 MiB,持久化 Cookie / localStorage 保留。
- 日志存储:单文件轮转从 2 MiB × 5 调整为 2 MiB × 3;新增日志保留天数(默认 7 天, 范围 1–90 天),清除旧日志仍走回收站,设置 v5 → v6 自动迁移。
- 网络性能:SSLContext 在进程内复用;Token 校验响应读取上限 512 KiB,避免异常响应 占满内存。
- 发行包:排除 Qt WebEngine DevTools、debug PAK 与 debug V8 snapshot,并排除 QtTest。
- 验收:190 项单元测试全绿;设置界面截图、两版打包、
--selftest、 文件版本与 SHA256 核对均通过。文件夹版 2,838 个文件 / 429.31 MiB, 单文件版 172,274,196 字节 / 164.29 MiB。
- 惰性浏览器:
main_window不再顶层导入token.capture,只有第一次真正取 Token 时 才加载 Qt WebEngine。主窗口启动与托盘常驻阶段不会创建QtWebEngineProcess。 - 后台启动检查:新增
StartupProbeWorker,窗口显示后再收紧敏感目录 ACL、留首次原始备份、 探测目标并判断“两边都没装”。同步必须等它结束,保证“先备份再写入”。 - 启动探测提速:
ensure_runtime_dirs(secure=False)只建目录;Windows 进程列表优先走 Toolhelp32 原生快照,不再额外启动tasklist;CC Switch 检查使用强制刷新,避免缓存过期。 - 开机启动安全:只把与当前 EXE 命令完全一致的项目显示为当前程序已启用。运行另一份便携 副本不会静默改写用户原有路径;设置保存失败时会恢复原始注册表命令。
- 发行包:
build_app.ps1自动生成带顶层目录的autoThuDeepSeekAPI-2.9.0-portable.zip,同时保留版本化单文件 EXE 和旧兼容名。 - 自检收尾:
--selftest显式释放 profile 并处理 DeferredDelete,修复单文件自检后 偶发残留 QtWebEngine 渲染进程。 - 验收:198 项测试、
compileall、git diff --check全通过;ZIP 顶层结构、两版--selftest、版本资源、SHA256、无 WebEngine 残留均核对。- 多文件目录版:2,838 文件 / 450,171,523 字节(429.32 MiB);
- ZIP:173,427,891 字节(165.39 MiB);
- 单文件版:172,280,579 字节(164.30 MiB);
- 隔离启动:扫描缓存完成后多文件版约 1.0–1.1 秒,程序内部启动约 0.6 秒;全新构建 前几次可能被 Defender 扫描拖慢到约 6–13 秒;单文件约 6.65 秒;
- 隔离测试前后开机启动项不变。
- 手动同步优先:开机启动后的 90 秒等待期内,手点「立即获取并同步」不再弹首次备份提示, 而是立即开始安全检查并跳过剩余等待。
- 安全中断:
StartupProbeWorker.request_cancel()为协作式中断;ACL 收紧和首次原始备份 一定先完成,之后才允许跳过目标探测。主窗口补做“两边是否都没痕迹”的判断,保留未安装提醒。 - 防重复启动:
_startup_pipeline_claimed防止手动提前启动后,app.py原来的延迟定时器 再触发第二轮启动检查。 - 验证:201 项测试、
compileall、pip check、git diff --check全通过;两版--selftest均为退出码 0,EXE 文件版本为 2.9.1,无新增 QtWebEngine 残留。- 多文件目录版:2,883 文件 / 452,854,896 字节(431.88 MiB);
- ZIP:173,428,299 字节(165.39 MiB);
- 单文件版:172,282,763 字节(164.30 MiB)。
- 全流程打通:内置浏览器取 Token、站点会话校验、DSH 凭据与 provider 写入、CC Switch 卡片与路由写入、写入前备份、一键还原。
- 全面排查并修掉的问题(明细见 CHANGELOG 的 2.0 一节):
- CC Switch:事务内
or短路导致_update_meta()不执行、apiFormat永远漏写、每次同步 都误判"有变化"并重写数据库(真 bug,已加回归用例)。 - CC Switch:加锁后重新核对卡片状态;INSERT 按实际列拼,兼容没有
is_current的旧库。 - DSH:统一模式下默认模型要指向真正被更新的 provider key(本地转发可能是
thu-local)。 - DSH:
providers.madmodel为 null/空视为空位;写盘前回读校验 + 原子落盘。 - 界面:等待对话框期间可能起第二轮同步(竞态);取 Token 启动失败会卡在"忙"; 退出时同步线程仍在跑;开机启动状态与注册表不一致、源码模式命令指错文件。
- 基础设施:数据根目录缓存(共享目录上重复探测很慢,且两实例会互相干扰探针); 设置 / 状态 / 备份索引 / 配置写入改原子写;日志目录不可写不再拖垮启动。
- 校验:站点回网页时不再白等一次 30 秒的对话请求。
- CC Switch:事务内
- 测试从 10 项扩到 36 项,覆盖目标写入、备份还原、模型列表解析、校验分支、设置迁移、 界面冒烟与基础设施。
- 写入内容预检(
preflight.py):目标以干跑模式跑一遍写入流程,渲染出的文本逐份校验 (能否解析、字段齐不齐、模型 id 与站点清单是否一致、Token 是否本次这个、默认模型是否有效); 不通过就跳过该目标的写入。主界面加了「校验写入内容」按钮,设置里可开关。- 真实配置实测:对本机 DSH + CC Switch 跑预检 → "将要写入的内容都合法", 且目标文件 mtime / size 前后完全一致(确认只读)。
- 一键还原 / 手动备份按钮:提到主界面;备份覆盖 DSH 凭据 + 各 profile + CC Switch 三个文件; 一键还原恢复"最早的一份备份",还原前先留底。
- 更多可配置项:静默取 Token 超时、DSH 默认模型、日志级别、关窗行为、同步前预检开关, 主界面加 20 / 60 / 180 分钟间隔预设。
- 测试 36 → 41 项;新增的"预检渲染必须与真写下的内容逐字一致"用例保证了两条路径不分叉。
- 2.1 打包、留档
releases/2.1/、提交195fd4d并打 tagv2.1推送。
- 修掉三个真 bug:
- DSH profile 带
!!js表达式时,写入前的回读校验用 safe 模式解析 → 拒收该标签 → provider 永远更新不了(本机复现:ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:js')。改为往返模式解析,!!js与注释原样保留。 - 旧版 CC Switch 数据库没有
is_current列 → SELECT 直接报错。现在按实际列名动态拼。 - 备份索引自愈放在写入路径上会重复记账(同一份备份两条记录)→ 移到读取路径。
- DSH profile 带
- 加固:文本写入加入"失败自动回滚";快照目录写
source.txt支持索引自愈; 日志按 2 MB × 5 轮转;profile 回写保持原有顶层缩进风格。 - 测试 41 → 52 项,覆盖:
!!js往返、索引自愈、写入回滚、旧库结构、WAL 备份、 中文/空格/超长嵌套路径、只读程序目录回退、profile 被删除不被复活、Token 过期与时钟偏差。 - 打包并留档
releases/2.2/(单文件版 + SHA256),提交并打 tagv2.2。
- "干净机器"与杀软误报只能在真实机器上确认:本机已用精简 PATH 跑过打包版
--selftest作为近似验证,但没有第二台干净机器。 - 站点改版(登录页结构变化、token 不再写 localStorage)无法在本地构造,只能靠捕获脚本的 多重兜底(Web Storage / fetch / XHR / Cookie / 页面文本)与"取不到就如实报错"。
- 定位根因:DSH 本体里
resolveReasoningLevel(model, options.reasoningEffort ?? profile.reasoning)—— 没有显式思考档时会回落到 provider 的reasoning,而DeepSeek-V4.1-Flash只支持low/high/max,所以固定写medium必然报UNSUPPORTED_REASONING_EFFORT;第一次运行 还没写agent-default-model.reasoningEffort,一定走回落,所以"第一次运行"最容易中招。 - 三条修法:provider 级
reasoning取所有模型支持档位的交集(没有就不写); 默认模型总是写显式且合法的reasoningEffort;预检把这两条作为硬性检查。 - 日志口径:把"无需改写"那句明确成文件比对(带 Token 指纹、注明不做网络校验), 并把真正的站点校验那条日志标成"向站点发起会话校验请求"。
- 真实配置已修:desktop/web 的
provider.reasoning由medium改为low, web 的默认模型补上high;改动前留底。 - 测试 52 → 56 项;打包并留档
releases/2.3/,提交并打 tagv2.3。
-
本地提交
92184e9(已 rebase 到你手改的4060add Update README之上)与本地 tagv2.3都已就绪,但github.com:443连续多次连不上,git push origin main与git push --force origin v2.3都没成功。 -
恢复网络后需要按顺序执行:
git push origin main git push --force origin v2.3 # 远端那个 v2.3 还指向 rebase 前的旧对象
-
另外顺手清掉了误提交的垃圾文件
$null(bash 里2>$null被当成重定向写出来的)。
- 首次运行直接开窗口:自动同步在没有缓存 Token 时不再走静默,而是直接打开浏览器
让用户登录(判断抽成
_should_capture_silently(),有回归用例)。 - 首次运行自动备份 + 永不清理:新增
backup_ops.auto_backup_on_first_run(), 启动时给每个目标文件留一份带original标记的备份;BackupManager.prune()改成 "每个源文件最早那份永远保留 + 普通备份按份数轮换"。顺带修掉同一快照目录里 多个cordis.patch.yml互相覆盖的 bug。 - 默认值:自动同步默认开、间隔 180 分钟、CC Switch 运行中默认不弹提醒; 设置文件 v3 一次性迁移,用户填过的间隔保留。
- Token 状态显示"有效至 …"(JWT
exp优先,否则取回时刻 + 5 小时); 顺手修正"没有 exp 就被当成过期"的边界。 - 桌面快捷方式:设置里新增按钮,用系统自带 WScript.Shell 生成
.lnk; 设置对话框改成可滚动。 - 开机启动检查:注册表值为空时自动重写,路径变了自动纠正。
- 测试 56 → 65 项,全绿。
- 打包并留档
releases/2.4/,提交并打 tagv2.4,推送成功。 ⚠️ 这一版的设置对话框是空白窗口(见 2.4.1),留档只为可回溯,不要用。
- 修「设置界面打不开」:2.4 套滚动区时先建了
QVBoxLayout(self)又新建布局并setLayout(),对话框最终没有有效布局,点「打开配置」出来是空白窗口。 现在只保留一个挂在对话框上的布局;加了回归用例test_settings_dialog_is_actually_openable。 - 「创建桌面快捷方式」提到主界面:之前只在设置里。现在主界面操作区也有按钮, 设置里复用同一入口。
- 版式:设置对话框加宽到 920(最小 900),修掉长复选框文案被右侧截断的问题;
tools/screenshot_ui.py --settings可以离屏出设置对话框截图(本轮靠它定位的)。 - 大检查:新增
test_every_button_is_wired(界面上每个按钮都必须接槽); 逐个 import 全部模块、compileall、69 项单元测试全绿。 ARCHITECTURE.md补齐设置迁移表、备份原始份保护、startup.py、ui 布局坑; README 增加「项目结构与开发」速查(文档索引 + 目录速查 + 测试/截图命令)。
- 重打包并留档
releases/2.4.1/(SHA256 见该目录),提交并推送。 - 打包版核对:从 exe 里解出的
PYZ确认APP_VERSION = 2.4.1,且main_window/settings_dialog/startup三个模块里都有快捷方式相关代码; 文件夹版与单文件版--selftest均通过。
- 双击打包版,点「打开配置」确认设置窗口正常显示
- 点「创建桌面快捷方式」,确认桌面出现
.lnk且双击能启动 - 首次运行时不再静默等超时,而是直接弹出浏览器窗口
需求、决策与验收清单单独写在 REQUIREMENTS-2.5.md(用户逐条确认过)。
- 修卡死:关掉登录窗口即按"取消取 Token"处理(新增
CaptureWindow.user_closed), 状态立刻释放;并新增「强行终止并重试」确认框(安全停止,不杀线程)。 - 恢复默认设置:
settings.reset_to_defaults()+ 设置对话框入口; 开机启动项与"上次同步"记录不动,并联动询问是否还原 private 位置。 - private 位置可改:位置指针放
%LOCALAPPDATA%\autoThuDeepSeekAPI\private-location.json;validate_private_dir()拒绝系统目录/Program Files/ProgramData/盘符根/临时目录/桌面;switch_private_root()搬迁 + 清理旧位置(用户文件不动),下次启动补删残留。 - 默认 180 分钟:核对默认值与迁移逻辑(不覆盖用户自定义间隔),用例覆盖。
- 干净卸载:
uninstall.clean_uninstall(),二次确认 + 可选还原首次备份 + 风险提示; 只删程序管理的东西,不删 exe(提示手动删),不写自删除脚本。 - 启动提醒:
target_traces()/both_targets_missing()判断"毫无痕迹"; 提醒一次后写状态(state.json的targets_note_ack),之后静默跳过同步。 - 顺序:
startup_then_sync()先桌面提醒、再没装提醒;桌面提醒的"已处理" 写在desktop_note_ack。 - 测试 69 → 88 项全绿;设置对话框截图核对版式(no webengine 依赖的用例避免离屏崩溃)。
- 打包 2.5 并留档
releases/2.5/(文件夹版与单文件版--selftest均通过; 从 exe 里解出PYZ核对:版本2.5,且 main_window / paths / uninstall / settings_dialog 里都有 2.5 的关键代码) - 端到端模拟点击:换 private 位置、恢复默认设置、干净卸载、两条启动提醒都按预期
- 真机人工确认:关窗后能重试、桌面提醒三选一、private 换位置、干净卸载
真机试 2.5 报回来的四个问题,逐条修完:
- 旧 private 删不干净:搬家/卸载前先放开占用(取消取 Token、
deleteLater()+ 显式DeferredDelete释放 WebEngine 档案、关闭文件日志句柄、让出单实例锁); 清空到只剩程序自己的东西时连目录一起删;万一还被占着就在指针里留重试标记,下次启动继续清。 - 删除改走回收站:
paths.recycle_path()(ShellSHFileOperation+FOF_ALLOWUNDO), 回收站不可用才永久删除并说明。 - 重启被判"程序已经在运行":抽
instance.py管锁,重启/卸载前主动让出, 新进程启动时重试拿锁(最多 6 秒)。 - 提醒前就建了 private:
paths.defer_creation()让启动早期只判断不落盘, 桌面提醒改到app.py最前面(建日志、建锁之前);按钮文案改成「退出程序去换本程序位置」。
验证:测试 88 → 96 项全绿;两个真机场景脚本(浏览器开着换位置、浏览器+日志+锁全开着卸载) 都跑通;设置对话框截图复检;版本 2.5.1 打包留档。
按要求做了一次全面检修,专门找"新功能 vs 旧逻辑"的冲突,修掉 7 处(明细见 CHANGELOG 2.5.2):
- 严重:同标签连续备份全挤进一个快照目录 → 旧备份永远清不掉、磁盘越占越多。
修法:只有显式
begin_snapshot()的批次才复用目录,普通备份各占一个。 - 自动同步会弹「强行终止并重试」→ 只有手动点同步才问,自动同步安静跳过。
- 搬位置/卸载期间可能被自动同步插入 → 新增
_maintenance+ 期间停计时器。 - 「恢复默认设置」还原 private 位置时绕过安全流程(会重现删不掉)→ 改走同一条流程。
- 备份轮换没走回收站 → 统一走
paths.delete_path()。 - 提醒文案里取
data_root()仍会先建目录 → 提醒全程"延迟创建"。 - 重复启动干等 6 秒 → 只有
--restarted才等 6 秒,正常启动 0.6 秒。
验证:测试 96 → 105 项全绿;真实启动流程冒烟通过;版本 2.5.2 打包留档。
用户报"干净卸载还是有问题"(截图:logs 报 WinError 32),并追加一条要求
"第一次同步之后不要改用户设置的推理强度(除非不合法)"。
- 找到 logs 删不掉的真根因:
setup_logging()反复配置时只把旧 handler 从 logger 上摘下来、没有close(),被摘下来的文件句柄一直抓着旧日志(程序里日志会配置多次: 启动、DEBUG、换位置)。现在重新配置时真的关掉,并把"开过的文件 handler"记表,close_file_handlers()一并关闭历史遗留的。 - 删不掉会重试:
remove_managed_data对失败项等 0.3 秒重试两次(Windows 释放句柄有延迟)。 - 思考档规则按用户要求收紧:provider 的
reasoning合法就一个字不改;只有 "有模型不支持"才改成公共档位;缺省不替用户加(新建 provider 才给公共兜底); 默认模型的reasoningEffort同理(合法保留、非法/缺失才补)。
验证:测试 105 → 109 项全绿;端到端脚本复现用户场景(日志配置两次 + 浏览器开着 + 单实例锁占着)卸载后全部进回收站、无失败项;版本 2.5.3 打包留档。
按"检查所有源码 bug"再扫一遍全部源码(含本轮没细读的 token/verifier.py、
token/site_models.py、selftest.py、ui/icon.py 等),修掉两处:
- 坏模型缓存会搞挂整次同步:
model-cache.json的at不是数字时float()抛异常, 一路冒到同步线程 → 这次同步直接失败。现在时间戳/结构异常一律当"没有缓存", 条目也过滤成字典;写缓存改成原子写。 - 旧同步线程收尾串台:
_busy()只看isRunning(),线程跑完但finished还没派发时 会放新同步进来,旧线程随后的finished又会清新线程的引用。 现在线程对象还在就算忙;三个回调都带"哪个线程",不是当前线程就忽略;worker.start()失败也会恢复界面状态。
另外把「强行终止并重试」的提示补了一句:网络校验那一步要等它返回(最多约半分钟)才停。
验证:测试 109 → 112 项全绿;版本 2.5.4 打包留档。
用户报"同步把别的模型的 api 改了",并给了真实 cordis.patch.yml(openaifse / glm /
ollama / madmodel 四个 provider)。
provider 认领规则里"baseURL 是本地转发(127.0.0.1:*/v1)就算我们的"这条太宽:
ollama 默认就是 127.0.0.1:11434/v1,于是在"只配了本地模型、没有 madmodel"的 profile 上,
我们会把 ollama 认成自己的 provider,接着改掉它的 apiKeyEnv 与模型列表。
- 认领从严:只认
apiKeyEnv: MADMODEL_API_KEY、baseURL指向清华站点、或"键名叫 madmodel 且没有别人的 apiKeyEnv 的本地转发"。 - 写前二次核对:
apiKeyEnv另有其人且域名不是清华站 → 报错退出、一个字节都不写。 - CC Switch 刷新同站点旧卡片时,只替换值是 JWT 或字段名正好是 Token 名的位置 (不再"名字里含 token 就算")。
- 用用户真实的
D:\1\cordis.patch.yml跑同步(统一 / 非统一两种模式):openaifse/glm/ollama/llm-deepseek/ui-settings-general逐项完全一致; 非统一模式默认模型仍是他自己的 glm(统一模式才会按该开关的语义切到 madmodel)。 - 新增 4 个用例(复刻多 provider 布局、不许认领别人的本地 provider、madmodel 键被占时报错且文件原样、 CC Switch 只刷 Token 字段);测试 112 → 116 项全绿;版本 2.5.5 打包留档。
用户报 isearcher 的 infini-ai 配置与默认模型被改,运行报
does not support reasoning effort "low" 与"模型不存在"。
2.5.5 只是不新认领别人,但老版本误改 isearcher 时已经往它身上写了
apiKeyEnv: MADMODEL_API_KEY;而 2.5.5 里"apiKeyEnv 是我们的就算我们的"这条还在,
残留标记于是让后续每次同步继续把它当自己人(继续改模型、写 reasoning: low、
勾统一时还把默认模型指过去)。
- 认领只认两个信号:
baseURL域名是清华站点;或键名就叫madmodel且确实像我们的。 "光有MADMODEL_API_KEY"不再算认领依据。 - 不勾统一时,profile 是否归我们管只看正文有没有清华域名或
madmodel:键 (去掉"出现过 MADMODEL_API_KEY",避免被残留标记带偏)。 - 扫到"带我们的标记、地址却不是清华站、键名也不是 madmodel"的 provider → 写提醒(日志 + 同步说明),建议「一键还原」,但程序不动它。
- 统一模式切换默认模型时明确记一条说明,避免被误当成 bug。
验证:改写/新增用例(isearcher 场景在统一模式下也一字不动且给出提醒、thu-local 不勾统一时整个 profile 跳过、madmodel 键被外人占用仍报错且文件原样);测试 116 → 117 项全绿;版本 2.5.6 打包留档。
按要求"再全面排查一次 + 一切交互不能改动用户其它配置 + 检查死逻辑"。
- DSH profile 改块级替换:原来整份 YAML 重写会把别人 provider 的流式列表空格、 注释对齐一起重排;现在只把"我们的 provider 块"和"真要改时的 agent-default-model 条目" 贴回原文件,其余原文照抄;只在值真的变了时才写。
- 凭据文件改行级替换:只改
MADMODEL_API_KEY:那一行(保留引号/行尾注释), 坏文件重建时才整份写。 - CC Switch 两个 JSON 改行级替换:只换
currentProviderCodex/apps.codex.proxy_route那一行,替换后json.loads校验;值没变不写;同名键不唯一则退回整份重排(仍只改值)。 - 「顺带刷新同站点旧卡片」默认关闭(会改别人的卡片),要的用户在设置里显式打开。
机械扫描全仓库"只有定义没人调用"的函数,删掉 6 个函数 + 1 个没用的 import
(backup.latest_entries、instance.is_held、paths.creation_deferred、paths.managed_paths、
procinfo.reset_cache、capture.close_window、site_models.models_equal 及其用例、selftest.socket)。
HTTP handler 的 do_GET / log_message 是框架回调,保留。
验证:测试 117 → 120 项全绿;版本 2.5.7 打包留档。
按要求"整个程序都要查 + 顺便检查死逻辑":发布前全量体检(静态扫描 + 逐模块读代码 + 最小实验复现)。先把疑点都写成最小实验跑一遍,确认是真 bug 再动手改。
- DSH 新增 provider 不再整份重写:原先没有 madmodel 的 profile 走整份重写分支时,
ruamel 会把同一文件里别人 provider 的流式列表空格、注释对齐一起重排。现在只往
我们那个条目(
llm-pi-ai)的providers:块末尾插入;同一文件还有别人时禁止整份重写, 定位不到我们的块就报错、这次不写。条目定位改成"整块文本匹配 id/name"(引号、 字段顺序都能认),数据层说条目已存在而文本层定位不到时绝不追加(否则会写重复条目)。 - CC Switch JSON 最小插入:
live-state.json缺apps.codex时只补出缺的键/中间层, 别的应用(claude)的紧凑写法一字不动;插入结果要过json.loads校验才用。 - 预检的全局检查失败会阻塞写入:Token 不是 JWT / 模型清单不合法时两个目标都不写
(以前这两项失败的 key 是
token/models,blocked_targets()认不出来,写入照跑)。 - 坏 JSON 不再崩启动:
settings.json/state.json是"合法 JSON 但不是对象" ([]/"x")时按"没有设置/状态"处理并记警告。 - 搬家失败不再丢数据:部分文件复制失败时中止 + 回滚新位置里刚复制的条目, 旧位置与位置指针都不动,用户修好问题重试即可。
- 其它:ESC 关桌面提醒不记"已处理"、干净卸载不删空目录、UI 写盘失败只提示不抛异常、
补清理用 resolve 后比较、备份自愈跳过并发消失的文件、单实例锁区分"已有实例/权限问题"、
日志级别即时生效、源码快捷方式改用 pythonw;删掉
SkipTarget、REPO_URL、 设置里的extra、状态里的last_verified_at及 3 个从未使用的函数参数。
验证:测试 120 → 131 项全绿;两个扫描器(符号级死代码 + 局部变量/参数/重复定义)均 0 条; 版本 2.5.9 打包留档。
按"正式版发布"的标准把界面完整走了一遍:逐个按钮对行为、逐条提示对文案、逐项设置对迁移, 重点是"前后添加的功能互相适配"。
- Qt 中文标准按钮:启动时加载
qtbase_zh_CN.qm,QMessageBox / 对话框的标准按钮 (是/否、确定/取消)不再是英文 —— 以前全中文界面里混着Yes/No、OK/Cancel。 - 恢复默认设置漏同步勾选:「顺带刷新同站点旧卡片的令牌」(2.5.7 新加)恢复默认后 界面仍勾着,点确定又写回去 —— 现在所有控件都跟着刷新,并有专门用例盯着。
- CC Switch 运行策略合并:原来"两个单选 + 不再弹提醒复选框"能组合出矛盾状态; 现在只有两个单选,旧设置走 v3→v4 迁移(勾过"不再弹"= 直接改,明确不勾 = 先提示)。 提示框里点「取消」不再保存"以后不再提示"。
- 手动粘贴 Token 的忙碌保护:以前会绕过
_busy()直接起写入线程;现在忙碌时拒绝并提示, 正常使用时把 Token 记进状态、刷新界面。 - 预检后台化:
PreflightWorker走后台线程,按钮禁用 + 状态栏进度;退出时最多等 5 秒, 等不到就交给进程退出处理(避免 Qt 销毁运行中的 QThread)。 - 桌面提醒不再重复:启动早期问过(例如按 ESC 没记状态)之后,主窗口不再问第二遍; 跨启动仍会提醒。
- 其它:按钮改名「设置…」、新增「关于…」(版本/作者/仓库/数据目录,可选中复制)、 状态区文字可复制、窗口最小尺寸、状态栏进度提示、两个目标都关时拒绝同步并说明原因、 托盘双击打开窗口、日志级别即时生效、设置底部文案改成用户能懂的表述、 「还原」加异常保护。
- 文档:新增
docs/USER-GUIDE.md(面向使用者的完整手册),README 同步更新。
验证:测试 131 → 140 项全绿;两个扫描器 0 条;离屏截图人工核对版式; 版本 2.6.0 打包留档。
真机反馈三条,逐条查到根因再修:
- DSH provider 地址写歪(web profile 里手写的
tsinghua那条,baseURL多了/chat/completions、models是id: "1"): 证据表明不是本程序写的(我们的常量只到/v1,"1"会被模型 id 校验丢掉); 但程序"只按域名认领 provider、不看路径"是个真弱点 —— 认领到坏地址会一直沿用。 现在认领时把路径纠正回/v1并说明;同文件里别人的坏地址只提醒、不动。 - 开机自动更新基本失败:从日志与注册表取证后,按四个层面加固 —— 开机启动延后 90 秒再同步、自动同步前做站点可达性预检、失败后 5 分钟重试最多 3 次 (成功补满预算)、开机静默失败自动打开一次登录窗口;另外日志文件被占用时改用 带进程号的备用名(以前会出现 0 字节日志,排查全靠猜)。
- CC Switch 不要出问题:加"提交前读回校验"(对不上就 rollback); 只读体检真机数据确认健康(6 张卡片里只有 deepseek-thu 是 current, settings.json 30+ 个键与其它应用的 proxy_route 全部原样)。
验证:测试 140 → 148 项全绿;两个扫描器 0 条;版本 2.6.1 打包留档。
回归与边界
- 脏数据 / 权限不足:坏 YAML、坏 JSON、坏索引、只读目录都有对应用例(2.2 起)
- 并发:重复点同步、自动同步与手动同步打架、CC Switch 抢写都有防护与用例
- 断网 / 超时:站点校验连不上时如实记"未能校验",沿用仍在有效期内的旧 Token
- 路径边界:中文 / 空格 / 超长嵌套路径、只读目录回退(
test_resilience.py) - 打包边界:干净机器(没装 VC 运行库、没有 Edge)上跑文件夹版与单文件版;确认杀软不误报
- UNC 路径(
\\vmware-host\Shared Folders\...):只能在真实共享目录上人工试
DSH
- 多 profile:删除后不复活、
!!js标签往返保留(test_resilience.py/test_targets.py) - provider 被占用时拒绝覆盖、
config结构异常时的报错路径(test_targets.py) - 坏凭据重建后 DSH 端能否自愈(重建会丢
records段):需要在本机 DSH 上人工确认
CC Switch
- 旧 schema(无
is_current)、WAL 模式、无变化不写库(test_resilience.py) - 数据库被真占用(CC Switch 正在运行)时的表现:需要开着 CC Switch 人工试
- 卡片被改名 / 同 id 不同 app_type:现有实现按
(id, app_type)定位,遇到异常结构只会报错,不覆盖
Token 与站点
- 站点改版:登录页结构变化、token 不再写 localStorage、接口改路径(只能等真发生)
- Token 过期 / 本机时钟偏差 / 没有 exp 时的状态显示(
test_resilience.py)
工程加固
- 写入失败时自动回滚(把刚备份的那份放回去)——2.2 已做
- 备份索引自愈(索引坏了先从快照目录重建)——2.2 已做
- 日志轮转(按天 + 按大小)——2.2 已做
- 界面上每个按钮都必须接槽(
test_every_button_is_wired)——2.4.1 已做 - 打包版代码核对(从 exe 里解出
PYZ,校验版本号与关键模块)——2.4.1 已做
这里原来还挂着一份 2.1 的计划清单(写入前预检、一键还原 / 手动备份、更多可配置项), 那些都已经落地,明细见 CHANGELOG 的 2.1 一节,就不再留重复的计划文字了。
- 站点前端结构与接口可能变化,模型列表与校验逻辑要跟着改。
- DSH 自己也会写 profile 文件;同步后建议重启 DSH,避免它用内存里的旧配置覆盖回去。
- CC Switch 正在运行时改数据库有被覆盖的风险。2.7 起默认先提示,且路由激活默认关闭。
- DPAPI 密文只能由同一 Windows 账户解密;浏览器档案受当前用户 ACL 保护,不额外加密。
- 单文件版启动比文件夹版慢(每次都要解包到临时目录),日常建议用文件夹版。
每个大版本都打了 tag,出问题可以按下面三种粒度回退(都不会动 private/ 里的运行数据):
git log --oneline --decorate # 看清哪个 commit 对应哪个版本
git revert <2.1 的提交> # 只回退 2.1 的改动,保留历史(推荐)
git checkout v2.0 # 直接回到 2.0 的代码
git checkout v2.1 # 再回到 2.1程序本身也有两层"回退":
- 一键还原(主界面按钮):把 DSH / CC Switch 的配置文件恢复成最早的备份(第一次修改前), 还原前会先备份当前状态,所以还原本身也能再退回来。
- 手动备份:随时留一份现状;备份都在
private/backups/,index.json里记着每份的来源与校验和。